Skip to content

fix(monitor): route assistant messages to channels for ACP .claude workdirs (#3286)#3287

Merged
aegis-gh-agent[bot] merged 3 commits into
developfrom
fix/3286-project-hash-dot-slug
May 13, 2026
Merged

fix(monitor): route assistant messages to channels for ACP .claude workdirs (#3286)#3287
aegis-gh-agent[bot] merged 3 commits into
developfrom
fix/3286-project-hash-dot-slug

Conversation

@OneStepAt4time

@OneStepAt4time OneStepAt4time commented May 13, 2026

Copy link
Copy Markdown
Owner

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: computeProjectHash preserved . inside segments. Claude Code replaces every . with -, so …/aegis/.claude/worktrees/x mapped to -…-aegis-.claude-… while CC's on-disk slug is -…-aegis--claude-…. discoverFromFilesystemFallback in session-transcripts.ts (and the equivalent in session-discovery.ts) looked in the wrong directory and never resolved session.jsonlPath.

Commit 2 — monitor.ts: Even with the slug fixed, checkSession guarded 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) lets checkSession flush the discovery-poll backlog before the watcher takes over.

End-to-end verification (with Telegram channel)

Instrumented tgApi and monitor paths, ran a fresh session in .claude/worktrees/verify-tg-trace2, waited 60 s, killed the session. Trace:

[poll]    jsonlPath=false isWatching=false            ← initial state
Transcripts (#1768 fallback): session mapped         ← slug fix worked
[fwd]     role=user × 4                              ← watcher-check fix worked
[poll]    jsonlPath=true  isWatching=true            ← watcher owns it now
[watcher] msgs=1
[fwd]     role=assistant type=text TRACE2_ASSISTANT_OK ← reaches channels.message
[tg]      → sendMessage thread=topic_id text="✨ TRACE2_ASSISTANT_OK"

Aegis's session-ended summary in the topic shows 5 msgs · No errors (4 user + 1 assistant) instead of the previous 0 msgs / 4 msgs.

Same session, no fix applied → no [fwd] role=assistant, summary 0 msgs.

Test plan

  • npx tsc --noEmit — clean.
  • src/__tests__/path-utils-909.test.ts — 6/6 pass (5 existing + 1 new for the .claude case).
  • src/__tests__/monitor-fixes.test.ts, monitor-idle-broadcast.test.ts, monitor-initial-offset.test.ts, jsonl-watcher.test.ts — 41/41 pass.
  • Runtime spot-check: computeProjectHash('/Users/…/aegis/.claude/worktrees/foo') now matches the on-disk ~/.claude/projects/…--claude-worktrees-foo.
  • End-to-end Telegram delivery: instrumented Aegis + real Telegram API. Assistant text lands in the topic after both fixes; was missing before either fix alone.

Files changed

  • src/path-utils.ts — 1-line addition to segment sanitization.
  • src/__tests__/path-utils-909.test.ts — 1 assertion for the .claude case.
  • 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 gate continues to fail on dashboard:tokens:gate, dashboard:clickable:gate, and 3 unrelated test cases on pristine origin/develop@c4cab08c. None touch monitor / channel / path-utils.

Aegis version

Developed with: v0.6.7-preview.1

@OneStepAt4time OneStepAt4time changed the title fix(monitor): replace dots in project-hash slug so JSONL is found for .claude workdirs (#3286) fix(monitor): route assistant messages to channels for ACP .claude workdirs (#3286) May 13, 2026

@aegis-gh-agent aegis-gh-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 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 .claude and worktree paths
  • ✅ Small, focused change (1 line + 2 test assertions)

Blocking issue — Gate 3 (CI green):

  • test (ubuntu-latest, 20) is failingTemplatesPage.test.tsx > deletes a template after confirmation times 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.

@aegis-gh-agent aegis-gh-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved.

Surgical fix for ACP .claude workdir message routing.

What's good:

  • Root cause clear: this.jsonlWatcher truthy 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
  • computeProjectHash dot replacement — .claude segments now produce -claude instead 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.

OneStepAt4time and others added 3 commits May 13, 2026 12:59
… .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).
@aegis-gh-agent aegis-gh-agent Bot force-pushed the fix/3286-project-hash-dot-slug branch from 792413e to fa41198 Compare May 13, 2026 10:59
@aegis-gh-agent aegis-gh-agent Bot merged commit 1e7e16d into develop May 13, 2026
17 checks passed
@aegis-gh-agent aegis-gh-agent Bot deleted the fix/3286-project-hash-dot-slug branch May 13, 2026 11:29
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.

1 participant