mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-19 15:32:05 +08:00
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:
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user