Skip to content

Improve Codex session detection and Telegram turn notifications#8

Merged
Headcrab merged 1 commit into
Headcrab:masterfrom
codex-k8s:codex/telegram-integration-fixes
May 7, 2026
Merged

Improve Codex session detection and Telegram turn notifications#8
Headcrab merged 1 commit into
Headcrab:masterfrom
codex-k8s:codex/telegram-integration-fixes

Conversation

@ai-da-stas
Copy link
Copy Markdown
Contributor

Summary

This PR contains fixes and small UX improvements discovered while dogfooding Telecodex with multiple
Codex sessions mapped to Telegram forum topics.

Changes:

  • Keep linked git worktrees as separate Telecodex environments instead of collapsing them into the main
    repository root.
  • Fix forked Codex rollout import by preserving the first session_meta, so forked/resumed sessions
    keep the correct cwd.
  • Harden Telegram history rendering:
  • local Markdown links like [repo](/home/...) are rendered as text instead of invalid Telegram HTML
    links;
  • Markdown formatting is ignored inside inline code;
  • history lines already prefixed with are normalized before blockquote rendering;
  • duplicate adjacent history entries are skipped;
  • history previews are shortened to reduce Telegram HTML/rate-limit issues.
  • Add telegram.completion_notify_usernames, which sends a separate completion FYI message after
    successful turns.
  • Clean up a few Clippy warnings touched while testing.

Context

Honest note: this PR is vibe-coded with Codex. I am primarily a Go developer and do not want to pretend
deep Rust expertise. The changes came from a real Telecodex setup with several parallel Codex sessions
in separate git worktrees and Telegram forum topics.

I tested the behavior locally and would appreciate careful maintainer review. I am also happy to split
this into smaller PRs or reshape the config/API if that fits the project better.

Testing

  • cargo test
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo build --release

Notes

No local deployment config or secrets are included in this branch.

The completion notification currently formats as Готово, @user fyi ✅ to match my local setup. If
preferred, I can make the message text fully configurable instead of only configuring the usernames.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

Walkthrough

This PR introduces a turn completion notification system with configurable user mention list, improves history preview formatting with deduplication and line normalization, refines git worktree and session metadata handling to treat worktrees as separate environments, updates markdown rendering for link handling and inline formatting rules, and applies minor code maintenance improvements for readability and optimization.

Changes

Turn Completion Notification System

Layer / File(s) Summary
Configuration & Validation
src/config.rs
New field completion_notify_usernames: Vec<String> added to TelegramConfig. Introduces normalize_completion_notify_username() to enforce formatting rules (trim, strip @, 5–32 chars, alphanumeric + underscores), then normalize and deduplicate case-insensitively during config validation.
Notification Dispatch
src/app/turns.rs
Adds notify_turn_completion() helper to dispatch Telegram notifications to configured usernames after successful turn completion. Introduces turn_completion_notification_text() utility to build localized notification messages. Refactors finalization path to trigger async notification before returning result.
Tests & Integration
src/app/tests.rs
New test formats_turn_completion_notification validates empty and populated username lists. Extends TelegramConfig initialization in test fixtures with completion_notify_usernames field.

History Preview Improvements

Layer / File(s) Summary
Normalization & Deduplication
src/app/presentation.rs
Adds normalize_history_quote_lines() and normalize_history_quote_line() helpers to preprocess lines starting with box-drawing character before HTML conversion. Introduces guard in merge_history_preview_entries() to skip consecutive entries with identical role and trimmed text. Reduces preview size constants: MAX_CHARS from 1200 to 650, MAX_LINES from 16 to 10.
Tests
src/app/tests.rs
New tests deduplicates_adjacent_identical_history_entries and normalizes_history_lines_that_already_use_quote_prefixes validate deduplication and quote prefix normalization behavior.

Git Worktree and Session Metadata Handling

Layer / File(s) Summary
Environment & Metadata Logic
src/codex_history.rs
In read_rollout_session_meta(), guards summary metadata population to only update when currently None, preventing overwrites in forked rollouts. In git_environment_root(), treats .git directory or file as environment root, enabling separate environments per git worktree instead of resolving via gitdir/commondir traversal.
Tests
src/codex_history.rs
New test keeps_first_session_meta_for_forked_rollout validates first session metadata is retained. Renamed and updated collapses_git_worktrees_into_one_environment to keeps_git_worktrees_as_separate_environments, with assertions adjusted to reflect worktree as distinct environment.

Markdown Rendering and Link Processing

Layer / File(s) Summary
Inline Formatting & Link Rendering
src/render.rs
Updates render_inline() to guard bold toggling outside code spans and apply backtick handling early for proper code block closure. Changes link rendering to detect telegram-style URLs (http, https, tg schemes) via new is_telegram_link_url() helper and render as anchors; non-telegram URLs rendered as plain text label followed by URL in parentheses.
Tests
src/render.rs
New tests leaves_local_markdown_links_as_text, renders_http_markdown_links_as_anchors, and does_not_render_markdown_inside_inline_code validate updated rendering rules.

Code Maintenance and Optimization

Layer / File(s) Summary
Refactoring
src/codex.rs
Expands conditional handling in run_app_server_turn() for RpcMessage::Response to separately process errors and extract turn_id for improved readability. Adds #[allow(clippy::too_many_arguments)] attribute to suppress lint warning on nearby function.
Optimization
src/telegram.rs
Removes redundant as i64 cast in preferred_image_file_id() max comparison. Adds #[allow(clippy::too_many_arguments)] to post_multipart_message() for lint suppression.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A whisper through the worktrees, notifications bloom,
Dedup the dusty histories, clear the cluttered room,
Links now find their rightful place—anchors true and bright,
With metadata preserved and environments in sight! 🌿✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main focus areas of the changeset: improvements to Codex session detection (git worktrees, session metadata) and additions to Telegram turn notifications (completion notifications).
Description check ✅ Passed The description comprehensively relates to the changeset, detailing specific fixes and improvements across multiple modules with clear explanations of the context, testing, and implementation choices.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ai-da-stas
Copy link
Copy Markdown
Contributor Author

И спасибо, классный проект!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/codex_history.rs (1)

953-961: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve repo/worktree root identity for nested subdirectories.

This change makes environment_identity_for_cwd() fall back to the exact cwd for any path below the repo/worktree root, so a session started from repo/src will no longer group with one started from repo/. That’s a broader behavior change than the PR objective and can split a single Telegram environment into multiple topics.

Suggested fix
 fn git_environment_root(cwd: &Path) -> Option<PathBuf> {
-    let git_path = cwd.join(".git");
-    if git_path.is_dir() {
-        return Some(normalize_path(cwd.to_path_buf()));
-    }
-    if git_path.is_file() {
-        return Some(normalize_path(cwd.to_path_buf()));
-    }
+    for ancestor in cwd.ancestors() {
+        let git_path = ancestor.join(".git");
+        if git_path.is_dir() || git_path.is_file() {
+            return Some(normalize_path(ancestor.to_path_buf()));
+        }
+    }
     None
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex_history.rs` around lines 953 - 961, git_environment_root currently
only checks the given cwd for a .git entry, causing environment_identity_for_cwd
to treat nested subdirectories (e.g., repo/src) as distinct identities; change
git_environment_root to walk up the ancestor chain from cwd to root, checking
each ancestor for a .git file or directory and return normalize_path of the
ancestor where .git is found (so sessions under the same repo/worktree share the
same identity). Update the logic in git_environment_root (referenced by
environment_identity_for_cwd) to loop through parents rather than only
inspecting cwd.
🧹 Nitpick comments (2)
src/app/turns.rs (1)

328-333: ⚡ Quick win

Make the completion notification text configurable (or at least a constant).

turn_completion_notification_text hardcodes a Russian-language string with an emoji ("Готово, … fyi ✅"). This bakes a locale assumption into a feature that's wired to a generic completion_notify_usernames config knob, and as you mentioned in the PR description, exposing the template is the natural next step. A simple path is to add an optional telegram.completion_notify_template (or completion_notify_text) string with the current value as the default, and substitute {usernames} (or {mentions}) at format time. This also makes the function testable for non-Russian setups without code changes.

Happy to follow up with a concrete patch in this PR or a separate one — your call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/turns.rs` around lines 328 - 333, turn_completion_notification_text
currently returns a hardcoded Russian string; make it configurable by reading an
optional config value (e.g., telegram.completion_notify_template or
completion_notify_text) and falling back to the current "Готово, {usernames} fyi
✅" template; update turn_completion_notification_text to accept the template (or
fetch it from the config) and format it by replacing a placeholder like
{usernames} (or {mentions}) with usernames.join(" "), returning None when the
list is empty, so tests and non-Russian setups can override the message without
code changes.
src/config.rs (1)

274-295: 💤 Low value

Minor: trim_start_matches('@') repeats — strip_prefix('@') matches a single sigil.

trim_start_matches('@') will silently accept "@@user" or "@@@user" by stripping every leading @. Switching to strip_prefix('@').unwrap_or(username) would normalize exactly one leading @, matching Telegram conventions and surfacing typos earlier via the ASCII allowlist if more @s sneak in. Optional polish.

♻️ Proposed tweak
 fn normalize_completion_notify_username(input: &str) -> Result<String> {
-    let username = input.trim().trim_start_matches('@');
+    let trimmed = input.trim();
+    let username = trimmed.strip_prefix('@').unwrap_or(trimmed);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/config.rs` around lines 274 - 295, The helper
normalize_completion_notify_username currently uses trim_start_matches('@')
which removes any number of leading '@' characters; change it to remove at most
one sigil by replacing that call with username =
input.trim().strip_prefix('@').unwrap_or(input.trim()); (or equivalent) so you
only normalize a single leading '@', preserving detection of inputs like
"@@user" by the ASCII allowlist/length checks in
normalize_completion_notify_username and keeping the rest of the validation
logic intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/codex_history.rs`:
- Around line 953-961: git_environment_root currently only checks the given cwd
for a .git entry, causing environment_identity_for_cwd to treat nested
subdirectories (e.g., repo/src) as distinct identities; change
git_environment_root to walk up the ancestor chain from cwd to root, checking
each ancestor for a .git file or directory and return normalize_path of the
ancestor where .git is found (so sessions under the same repo/worktree share the
same identity). Update the logic in git_environment_root (referenced by
environment_identity_for_cwd) to loop through parents rather than only
inspecting cwd.

---

Nitpick comments:
In `@src/app/turns.rs`:
- Around line 328-333: turn_completion_notification_text currently returns a
hardcoded Russian string; make it configurable by reading an optional config
value (e.g., telegram.completion_notify_template or completion_notify_text) and
falling back to the current "Готово, {usernames} fyi ✅" template; update
turn_completion_notification_text to accept the template (or fetch it from the
config) and format it by replacing a placeholder like {usernames} (or
{mentions}) with usernames.join(" "), returning None when the list is empty, so
tests and non-Russian setups can override the message without code changes.

In `@src/config.rs`:
- Around line 274-295: The helper normalize_completion_notify_username currently
uses trim_start_matches('@') which removes any number of leading '@' characters;
change it to remove at most one sigil by replacing that call with username =
input.trim().strip_prefix('@').unwrap_or(input.trim()); (or equivalent) so you
only normalize a single leading '@', preserving detection of inputs like
"@@user" by the ASCII allowlist/length checks in
normalize_completion_notify_username and keeping the rest of the validation
logic intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a7076f3d-889e-4ade-bec6-5945363cebdb

📥 Commits

Reviewing files that changed from the base of the PR and between 3bfa986 and f25bd29.

📒 Files selected for processing (8)
  • src/app/presentation.rs
  • src/app/tests.rs
  • src/app/turns.rs
  • src/codex.rs
  • src/codex_history.rs
  • src/config.rs
  • src/render.rs
  • src/telegram.rs

@Headcrab Headcrab merged commit dbda356 into Headcrab:master May 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants