Skip to content

fix(agent): a torn JSONL append never refuses the session - #4297

Open
code-yeongyu wants to merge 1 commit into
mainfrom
fix/4295-jsonl-torn-append
Open

code-yeongyu wants to merge 1 commit into
mainfrom
fix/4295-jsonl-torn-append

Conversation

@code-yeongyu

@code-yeongyu code-yeongyu commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Part of #4295

Summary

A JSONL session (harness JsonlStorage, used by the experimental server and session-worker) refused to open after an append torn by a full disk. The next commit landed glued to the fragment, and it reused the failed commit's sequence, so every reopen threw Invalid JSONL storage <file>: line N.

  • Repair before every append. A failed append is recorded. Before the next append its bytes (a fragment, or the whole line when it landed but the write reported failure) are cut, so nothing is glued and the file matches what callers were told.
  • No sequence reuse. The sequence high-water mark moves past a failed commit.
  • Open never refuses for a damaged record. A line that is not JSON is damage: the first suffix that starts a record and replays is kept (a torn append glued to the next one), the rest is set aside. A framed record that does not replay (a reused sequence, a missing parent) is set aside. Every record before and after is kept. A valid JSON record of a kind this version cannot frame still refuses the session, so a downgrade never strips a newer version's records from the live file.
  • Backup, never deleted. Damaged bytes are copied to <file>.torn-<time> atomically (not listed as a session), then the session file is rewritten atomically once.
  • One report per repair. onRecovery on JsonlStorageOptions / JsonlSessionRepoOptions receives a typed JsonlStorageRecovery (cause: disk_full | write_failed | unknown, backupPath, droppedRecords, a user-facing message). A repair the disk refuses on open is deferred to the next append, which fails with that error instead of appending after the damage.

The main CLI/RPC SessionManager path is a separate PR (backup + typed notice).

Evidence

  • RED on main 96f8ac1: jsonl-storage-torn-append.test.ts 4/4 fail (Invalid JSONL storage s.jsonl: line 4, cause Invalid JSONL transaction: not valid JSON).
  • GREEN at this head: the six jsonl suites, 73/73, including every tear offset of a record (1..160 characters) with a same-process retry. tsc --noEmit, oxlint --deny-warnings, oxfmt --check, lint ratchet (baseline lowered: one ! removed) and the changelog gate pass.
  • The existing test rejects a complete malformed final line without rewriting now expects the line set aside; the two refusal tests for unknown record kinds are unchanged.
  • Simulated ENOSPC only (a FileSystem stub that lands N characters, then fails); no real disk was filled.

Merge order (with bugwatch)

#4297 (A1) -> #4303 (B) -> #4300 (A2) -> #4305 (C) last. #4305 touches the same flushed-append block in SessionManager._persist as #4300, so it rebases after #4300 merges. #4305 carries Fixes #4295; the others are Part of #4295.

Set damaged records aside in <file>.torn-<time>, keep every record around them, repair before the next append, never reuse a failed commit's sequence, and report each repair once through onRecovery.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant