fix(observer): uuid-dedup in parseLines (C-1 root fix for quirk #101)

Bug: Claude Code's transcript JSONL file accumulates duplicated context-
rebuild snapshots — the same entry re-printed with the SAME `uuid`. Without
dedup, session_turn / task_size / events double-count, and session_turn
becomes non-monotonic across episodes parsed at different file-growth
states. Live evidence: episodes-2026-05.jsonl lines 14/15/16 of the same
session showed session_turn 139 → 140 → 91 (backwards in time). Probe
on transcript 553717ec: 22400 entries, only 6074 unique uuid (68% dup
rate); real user prompts 264 total vs 92 unique-uuid.

Fix: parseLines now tracks a `seenUuid` Set and skips entries whose uuid
has already been encountered (keep-first). Entries without `uuid`
(synthetic test fixtures) pass through unchanged. All downstream functions
(findTurnStart, extractEnvironment, extractTaskSize, etc.) operate on the
deduped entries array, so the fix is single-point and total.

Tests: new `parseTranscript — uuid-dedup` describe block covers
(1) duplicated-uuid prompts collapse → session_turn counts once,
(2) distinct-uuid entries preserved (no over-dedup),
(3) no-uuid entries pass through (synthetic-fixture safety),
(4) duplicated-uuid assistant turns → tool_calls / files_touched counted once.
110/110 parser tests green (was 106).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-20 11:00:50 +03:00
parent 1ba8b6e590
commit 94f831f7d1
2 changed files with 117 additions and 1 deletions
+15 -1
View File
@@ -23,15 +23,29 @@ function parseLines(text) {
const entries = [];
let broken = 0;
let total = 0;
// quirk #101 root fix: Claude Code's transcript file accumulates duplicated
// context-rebuild snapshots — the same entry is re-printed with the SAME
// `uuid`. Without dedup, session_turn / task_size / events double-count and
// session_turn becomes non-monotonic across episodes parsed at different
// file-growth states. Keep the first occurrence per uuid; entries without a
// uuid (synthetic test fixtures) pass through unchanged.
const seenUuid = new Set();
for (const line of String(text || '').split('\n')) {
const trimmed = line.trim();
if (!trimmed) continue;
total += 1;
let e;
try {
entries.push(JSON.parse(trimmed));
e = JSON.parse(trimmed);
} catch {
broken += 1; // broken line — counted for parse_gap, never thrown
continue;
}
if (e && e.uuid) {
if (seenUuid.has(e.uuid)) continue;
seenUuid.add(e.uuid);
}
entries.push(e);
}
return { entries, broken, total };
}