Session JSONL corrupted by non-atomic append when process killed mid-write — `--resume` fails with JSON parse error

Resolved 💬 3 comments Opened Apr 23, 2026 by MathieuHuot Closed Apr 27, 2026

Summary

When the Claude Code process is terminated mid-write to its session JSONL (e.g. SIGKILL from parent, stdio pipe closed on VS Code Remote-SSH disconnect, laptop sleep breaking the tunnel), the partially-written record leaves a truncated stub on disk. The next append then starts writing from the same file position with no newline separator, producing two concatenated JSON objects on a single line that parse as neither. claude --resume <session-id> fails with a cryptic JSON Parse error: Expected ':' before value in object property definition and the session cannot be resumed.

Environment

  • Claude Code VS Code extension 2.1.118 (linux-x64); binary 2.1.117 / 2.1.118
  • Linux remote accessed via VS Code Remote-SSH
  • Trigger in our case: Remote-SSH tunnel drop over Tailscale / laptop sleep while Claude was streaming a response

Expected

Session files should remain parseable as newline-delimited JSON regardless of when the process is terminated. A partially-written record should be either fully absent or fully present — never a truncated stub followed by another record on the same line.

Actual

Example corrupt line (redacted, one bad line out of ~2800):

{"type":"pr-link","sessionId":"<redacted>","prNu{"parentUuid":"<redacted>","isSidechain":false,"promptId":"<redacted>","type":"user", ...}

The first ~74 bytes are a truncated pr-link record whose write was cut off at "prNu. The subsequent record was then appended to the same line with no separator. JSON.parse fails on the whole line; --resume bails out. Only the last-written line is corrupt — all prior records parse correctly.

Proposed fix

Make record appends atomic. Minimal-change options:

  1. Build the full record bytes (record + trailing \n) in memory and issue a single write(2) on an O_APPEND fd. Much less likely to tear than buffered fwrite+fflush pairs.
  2. For larger records, write to a sibling .tmp and commit via rename-append.

Either prevents the "two records on one line" pattern.

Workaround

External repair script that validates the JSONL line-by-line, locates the inner {" split boundary on the one corrupt line, keeps whichever half is valid JSON, and drops the truncated stub. Safe to run idempotently. Happy to share the ~100 lines of Python if useful.

Severity

Minor but surfaces badly. Only the last record is ever affected, but the resume failure shows up as a JSON parse error with no obvious next step, and losing the active conversation context is disruptive enough that users blame the disconnect rather than a file-write bug.

Related issues

  • #49876 — similar family (non-atomic JSONL appends), different manifestation: null-byte runs on WSL2 causing silent context truncation. Ours is a single-process kill on Linux/Remote-SSH causing loud parse-error failure.
  • #20992 → #20637 — both closed as duplicates. About concurrent-writer races on macOS (~/.claude.json and subagent workloads). Ours is single-writer.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗