fix(monitor): route assistant messages to channels for ACP .claude workdirs (#3286)#3287
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
🔍 Review — Requesting changes (CI not green)
Code review:
- ✅ The fix is correct:
.replace(/\./g, "-")mirrors Claude Code slug behavior - ✅ Root cause analysis is thorough — clear explanation of why JSONL lookup fails
- ✅ Test coverage added for
.claudeand worktree paths - ✅ Small, focused change (1 line + 2 test assertions)
Blocking issue — Gate 3 (CI green):
test (ubuntu-latest, 20)is failing —TemplatesPage.test.tsx > deletes a template after confirmationtimes out at 5000ms- This appears to be a Node 20-specific flaky test (unrelated to your path-utils change), but CI must be green per merge gate rules
Recommendation: Re-trigger CI (gh run rerun) to see if it passes on a retry. If the flaky test persists, please address it (increase timeout or fix the test) so CI goes green. I'll approve as soon as CI is clean.
Non-blocking observation: PR body mentions "3 pre-existing failures" — but test (ubuntu-latest, 20) is the only one showing in CI. If the others are gate-level, they should be documented.
👁️ Gate 3 must pass.
Contributor
There was a problem hiding this comment.
✅ Approved.
Surgical fix for ACP .claude workdir message routing.
What's good:
- Root cause clear:
this.jsonlWatchertruthy check missed sessions where watcher exists but hasn't subscribed to this specific session yet (discovery poll race) - Fix:
this.jsonlWatcher?.isWatching(session.id)— per-session check instead of global instance check computeProjectHashdot replacement —.claudesegments now produce-claudeinstead of being passed through, matching Claude Code's slug behavior- Test added for the dot-replacement case with two path examples
- Comment explains the race condition well
CI fully green. Ready to merge.
… .claude workdirs (#3286) Claude Code's project-directory slug replaces every `.` in a path segment with `-`. Aegis's computeProjectHash preserved the dot, so workdirs like `<repo>/.claude/worktrees/x` produced a slug that didn't match the actual `~/.claude/projects/-...-aegis--claude-worktrees-x/` directory. discoverFromFilesystemFallback then looked in the wrong folder, the monitor never found the JSONL, and channels.message('message.assistant') never fired — so assistant replies never reached Telegram / Slack / webhooks / the legacy transcript endpoint. Add a `.replace(/\./g, '-')` to segment sanitization and an assertion covering the .claude case. Closes #3286
…ession (#3286) The poll-cycle hand-off between checkSession and the JsonlWatcher missed historical entries discovered via the filesystem-fallback path. The guard `!this.jsonlWatcher` only checked whether the watcher *instance* existed (it always does, once initialized). On the discovery poll — where readMessagesForMonitor's fallback just set jsonlPath and read every existing JSONL entry — those entries were dropped, then the watcher attached at end-of-file and saw no further changes for content that pre-dated its attachment. Switch to `!this.jsonlWatcher?.isWatching(session.id)` so checkSession forwards historical entries on the exact poll where the fallback discovers the JSONL. The watcher takes over for subsequent appends. Verified end-to-end with the Telegram channel: assistant messages now fan out to channels for ACP sessions whose workdir was discovered via the filesystem fallback (combined with the slug-mismatch fix in the previous commit). Closes #3286 (combined with previous slug-mismatch fix).
792413e to
fa41198
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #3286. Two coupled root causes prevented
message.assistant(and the user-facing equivalent) from reaching any notification channel (Telegram, Slack, webhook, email) — or/v1/sessions/:id/transcript— for sessions whose workdir contains a dotted segment (e.g..claude/worktrees/...).Commit 1 —
path-utils.ts:computeProjectHashpreserved.inside segments. Claude Code replaces every.with-, so…/aegis/.claude/worktrees/xmapped to-…-aegis-.claude-…while CC's on-disk slug is-…-aegis--claude-….discoverFromFilesystemFallbackinsession-transcripts.ts(and the equivalent insession-discovery.ts) looked in the wrong directory and never resolvedsession.jsonlPath.Commit 2 —
monitor.ts: Even with the slug fixed,checkSessionguarded forwarding with!this.jsonlWatcher(instance presence) instead of per-session watching state. On the exact poll where the fallback discovered the JSONL and read every existing entry, those entries were dropped — and the watcher then attached at end-of-file and saw no further changes for the content that pre-dated its attachment. Result: 4 historical user prompts forwarded only by coincidence (timing-dependent), 0 assistant entries forwarded. Switching the guard to!this.jsonlWatcher?.isWatching(session.id)letscheckSessionflush the discovery-poll backlog before the watcher takes over.End-to-end verification (with Telegram channel)
Instrumented
tgApiandmonitorpaths, ran a fresh session in.claude/worktrees/verify-tg-trace2, waited 60 s, killed the session. Trace:Aegis's session-ended summary in the topic shows
5 msgs · No errors(4 user + 1 assistant) instead of the previous0 msgs/4 msgs.Same session, no fix applied → no
[fwd] role=assistant, summary0 msgs.Test plan
npx tsc --noEmit— clean.src/__tests__/path-utils-909.test.ts— 6/6 pass (5 existing + 1 new for the.claudecase).src/__tests__/monitor-fixes.test.ts,monitor-idle-broadcast.test.ts,monitor-initial-offset.test.ts,jsonl-watcher.test.ts— 41/41 pass.computeProjectHash('/Users/…/aegis/.claude/worktrees/foo')now matches the on-disk~/.claude/projects/…--claude-worktrees-foo.Files changed
src/path-utils.ts— 1-line addition to segment sanitization.src/__tests__/path-utils-909.test.ts— 1 assertion for the.claudecase.src/monitor.ts— 4 lines (guard + 2-line comment explaining the hand-off).3 files, +11/-3 lines.
Gate notes (pre-existing, not introduced here)
npm run gatecontinues to fail ondashboard:tokens:gate,dashboard:clickable:gate, and 3 unrelated test cases on pristineorigin/develop@c4cab08c. None touch monitor / channel / path-utils.Aegis version
Developed with: v0.6.7-preview.1