fix(claude-sync): skip subagent transcripts to prevent main session corruption (#854)

The session indexer scans ~/.claude/projects recursively via
findFilesRecursivelyCreatedAfter, which descends into per-session
subagents/ directories. Claude writes subagent transcripts at:

  ~/.claude/projects/<encoded-cwd>/<session-id>/subagents/agent-<id>.jsonl

These files repeat the parent session's sessionId. When indexed as
standalone sessions they upsert over the parent row and overwrite its
jsonl_path with the subagent path, corrupting the main session record
(the sidebar then points at, and renders, the subagent transcript).

Add a single isSubagentTranscript() guard (path segment named
"subagents") and apply it in both the recursive scan and the
single-file watcher path.

Co-authored-by: Haile <118998054+blackmammoth@users.noreply.github.com>
This commit is contained in:
Karel Bourgois
2026-06-18 14:37:37 +02:00
committed by GitHub
parent e88539170e
commit a12ca8eed3

View File

@@ -25,6 +25,21 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
private readonly provider = 'claude' as const; private readonly provider = 'claude' as const;
private readonly claudeHome = path.join(os.homedir(), '.claude'); private readonly claudeHome = path.join(os.homedir(), '.claude');
/**
* Returns true when a JSONL file is a subagent transcript rather than a
* top-level session.
*
* Claude stores subagent transcripts under a `subagents/` directory, e.g.
* `~/.claude/projects/<encoded-cwd>/<session-id>/subagents/agent-<id>.jsonl`.
* Those files repeat the parent session's `sessionId`, so indexing them as
* standalone sessions overwrites the parent row's `jsonl_path` and corrupts
* the main session record. The recursive scan in `synchronize()` reaches
* them, so both entry points must skip them.
*/
private isSubagentTranscript(filePath: string): boolean {
return path.normalize(filePath).split(path.sep).includes('subagents');
}
/** /**
* Scans ~/.claude/projects and upserts discovered sessions into DB. * Scans ~/.claude/projects and upserts discovered sessions into DB.
*/ */
@@ -38,6 +53,10 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
let processed = 0; let processed = 0;
for (const filePath of files) { for (const filePath of files) {
if (this.isSubagentTranscript(filePath)) {
continue;
}
const parsed = await this.processSessionFile(filePath, nameMap); const parsed = await this.processSessionFile(filePath, nameMap);
if (!parsed) { if (!parsed) {
continue; continue;
@@ -66,6 +85,9 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
if (!filePath.endsWith('.jsonl')) { if (!filePath.endsWith('.jsonl')) {
return null; return null;
} }
if (this.isSubagentTranscript(filePath)) {
return null;
}
const nameMap = await buildLookupMap(path.join(this.claudeHome, 'history.jsonl'), 'sessionId', 'display'); const nameMap = await buildLookupMap(path.join(this.claudeHome, 'history.jsonl'), 'sessionId', 'display');
const parsed = await this.processSessionFile(filePath, nameMap); const parsed = await this.processSessionFile(filePath, nameMap);