Skip to content

Commit 1e7e16d

Browse files
fix(monitor): route assistant messages to channels for ACP .claude workdirs (#3286)
* fix(monitor): replace dots in project-hash slug so JSONL is found for .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 * fix(monitor): forward messages when watcher hasn't started for THIS session (#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). * chore: trigger CI refresh
1 parent 35fde64 commit 1e7e16d

3 files changed

Lines changed: 11 additions & 3 deletions

File tree

src/__tests__/path-utils-909.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,9 @@ describe('Issue #909: computeProjectHash cross-platform normalization', () => {
2121
it('returns fallback for empty input', () => {
2222
expect(computeProjectHash('')).toBe('-');
2323
});
24+
25+
it('replaces dots in segments to match Claude Code slug (Issue #3286)', () => {
26+
expect(computeProjectHash('/home/me/.claude/repo')).toBe('-home-me--claude-repo');
27+
expect(computeProjectHash('/Users/x/Documents/aegis/.claude/worktrees/foo')).toBe('-Users-x-Documents-aegis--claude-worktrees-foo');
28+
});
2429
});

src/monitor.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -627,8 +627,11 @@ export class SessionMonitor {
627627
const result = await this.sessions.readMessagesForMonitor(session.id);
628628
const prevStatus = this.lastStatus.get(session.id);
629629

630-
// Forward messages only when watcher is NOT active (fallback polling path)
631-
if (!this.jsonlWatcher && result.messages.length > 0) {
630+
// Forward messages only when watcher is NOT active for THIS session (#3286).
631+
// Checking the watcher instance alone misses the discovery poll where the
632+
// fallback just set jsonlPath but the watcher hasn't started yet — entries
633+
// read here would otherwise be dropped before the watcher subscribes.
634+
if (!this.jsonlWatcher?.isWatching(session.id) && result.messages.length > 0) {
632635
this.rateLimitedSessions.delete(session.id);
633636
for (const msg of result.messages) {
634637
await this.forwardMessage(session, msg);

src/path-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function computeProjectHash(workDir: string): string {
1515
const segments = withLowerDrive
1616
.split('/')
1717
.filter(Boolean)
18-
.map((segment) => segment.replace(/:/g, '').replace(/\s+/g, '-'));
18+
.map((segment) => segment.replace(/:/g, '').replace(/\s+/g, '-').replace(/\./g, '-'));
1919

2020
if (segments.length === 0) return '-';
2121
return `-${segments.join('-')}`;

0 commit comments

Comments
 (0)