Fix: repair an incomplete JSONL tail before resuming session appends, so the next entry doesn't get lost too (#928) #1764
kaluli123123
started this conversation in
Bug reports
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Summary
A crash mid-write can leave a session's
.jsonlfile's final record truncated. This is #928, one of the "related reports" under the still-open tracker #1380 (Make persisted session and configuration state crash-safe). Reproduced fresh on currentmain(06860844e) with a new failing-first regression test — not just read from the source.Root cause
parseEntriesFromBuffer(and friends) already silently skip any line that failsJSON.parse, including a truncated trailing fragment — so in-memory state after resume looks fine. But the raw bytes of the fragment are never removed from disk, and the hot append path in_persist()callsappendFileSync()directly onto the existing file:The next real entry lands right after the fragment, producing one combined malformed line that also fails to parse — so the new entry is silently lost too, and this repeats on every future crash/resume cycle at that same file. Confirmed with a direct repro: write a valid header + message + a truncated trailing record (no closing brace, as a crash mid-
fwritewould leave it), open the session, append a new message, reload — the new message is gone.Fix
All in
packages/coding-agent/src/core/session-manager.ts:loadEntriesFromFileWithTail/loadEntriesFromFileAsyncWithTailexpose{ entries, incompleteTail }; the existingloadEntriesFromFile/loadEntriesFromFileAsynckeep their originalFileEntry[]signature as thin wrappers, so the one other call site (context-tree.ts) is unaffected.SessionManager.setSessionFile(used by both the syncSessionManager.openand, via a newpreloadedIncompleteTailparameter, the asyncSessionManager.openAsync) now calls a new_repairIncompleteTail()after loading: it quarantines the fragment's raw bytes to a<file>.incomplete-tail-<ts>-<id>.jsonlsidecar for forensic recovery, logs a warning, and immediately rewrites the session file through the existing atomic temp-file+rename path (_rewriteFile) — so the corrupt bytes are gone from disk before any subsequent append can concatenate onto them, not just before the next write happens to occur.Acceptance criteria from #928 — all covered by new tests
getLogger("coding-agent.session-manager"), plus the quarantine sidecar).Tests
Added 6 tests to
packages/coding-agent/test/session-manager/file-operations.test.ts, all verified to fail against pre-fixmainand pass after:loadEntriesFromFileWithTail/-AsyncWithTailreport a truncated final record without dropping earlier entries.SessionManager.openandSessionManager.openAsyncboth: quarantine the fragment, keep earlier records, physically truncate the on-disk file immediately, and a subsequently appended entry round-trips through a reload instead of being lost.Also ran the full session-manager-adjacent suite for regressions:
test/session-manager/,session-manager-flush.test.ts,session-manager-git-state.test.ts,sdk-session-manager.test.ts,context-tree.test.ts— 161 + 14 passed, 0 failed.npx tsgo -p tsconfig.json --noEmitand the rootnpm run check(biome, tsgo, installer render, browser smoke) both pass. Added apackages/coding-agent/.changes/fragment per the changelog-fragment CI check.Patch
Branch: https://github.com/kaluli123123/prime-agent/tree/fix/session-jsonl-incomplete-tail-repair
Diff: main...kaluli123123:prime-agent:fix/session-jsonl-incomplete-tail-repair
Happy to open a PR from this branch if a maintainer wants to invite implementation, per CONTRIBUTING.md.
All reactions