[BUG] Session JSONL appender silently dies mid-session while the process keeps running — history becomes memory-only and is lost on resume
[BUG] Session JSONL appender silently dies mid-session while the process keeps running — history becomes memory-only and is lost on resume
Summary
In long-lived interactive sessions, the process that appends turns to the top-level session transcript (~/.claude/projects/<proj>/<sid>.jsonl) stops writing partway through a session that is otherwise still fully alive and working. The same process keeps writing its sibling files — <sid>/subagents/*.meta.json and <sid>/tool-results/*.txt — for days afterward, so this is not a crash and not a filesystem rollback: the top-level transcript appender specifically has died while the rest of the process runs on.
Because the live conversation continues only in the process's memory after that point, the on-disk transcript is a stale fragment. The loss is invisible until the tab is slept/closed and --resumed, at which point the replay contains only the fragment and everything after the freeze is gone.
This is distinct from the truncation/write-delay issues below (see "Not a duplicate of").
Signature (how to recognize it)
For an affected session, on disk:
- The top-level
<sid>.jsonllast-modified time and last timestampeduser/assistantturn are frozen at some early point… - …while
<sid>/tool-results/*.txtand<sid>/subagents/*.meta.jsonfor the same session keep getting written for hours or days after that freeze. - No unclean shutdown occurred inside the loss window (verified against boot times /
last). - The transcript never got smaller on disk — the missing tail was never written at all, so tail-truncation recovery (#53821) does not apply.
Concrete case (Claude Code v2.1.x, Linux):
- Session froze its
.jsonl3 minutes into what became a 6-day session (last on-disk turn ~Jul 14 11:24). - That same session's
tool-results/*.txtwere still being written Jul 19, andsubagents/*.meta.jsonJul 20 — 5–6 days past the transcript freeze. - On a machine-wide check for one affected day: zero
.jsonlwrites landed anywhere, whiletool-results//subagents/writes for live sessions landed fine. - Observed across ~12 sessions since ~late June, disproportionately the long-lived, always-open tabs. Reproduces across multiple v2.1.x releases (seen through 2.1.217).
Impact
- Silent, unrecoverable history loss. Nothing on disk ever held the lost turns, so no restore/backup tool can recover them.
- The user has no signal: the session looks healthy (it keeps responding), and even the transcript file's mtime keeps moving, because the dead-writer process still appends untimestamped metadata records (
last-prompt,ai-title,mode,permission-mode) on resume/close — so mtime is a false health indicator. - Discovered only on resume, typically after deliberately sleeping/closing the tab — i.e. exactly when the memory-only tail is destroyed.
Steps to reproduce
Not yet deterministically reproducible; it correlates with long-lived sessions kept open for days. Detection is reliable even without a repro:
- Pick a session that has been open a long time.
- Compare
stat -c %y <sid>.jsonland the last timestampeduser/assistantentry inside it against the newest mtime under<sid>/tool-results/and<sid>/subagents/. - If the sibling directories are hours/days newer than the transcript's last real turn, the appender for that session has died.
Expected behavior
- The transcript appender should never stop while the session runs; if a write fails, it should retry/reopen rather than silently give up for the remainder of the session.
- Failing that, Claude Code should detect that its own transcript writes have stalled (it is still writing sibling files, so it can compare) and surface a warning, so the session can be exported before its memory-only tail is lost.
Not a duplicate of
- #58463 (write delay:
tool_usenot written untiltool_use_resultarrives) — there the event eventually lands; here the appender dies permanently and later turns never land at all. - #75435 (frozen JSONL after compaction, breaking hooks that read
transcript_path) — same frozen-transcript symptom, but that report ties the freeze to a compaction event and is scoped to hook staleness; this occurs without a compaction boundary and results in permanent user-facing history loss. - #53821 (closed; transcript truncated by power loss / abrupt termination) — that is tail corruption from an unclean write; here there is no unclean shutdown and the tail was never written, so "load from last valid entry" recovers nothing.
Environment
- Claude Code 2.1.x (observed through 2.1.217), Linux, interactive TTY sessions.
- Reproduced across multiple v2.1.x releases over ~4 weeks.
Diagnostic snippet
# Flag sessions whose transcript is frozen while the process keeps working:
for js in ~/.claude/projects/*/*.jsonl; do
[ -d "${js%.jsonl}" ] || continue
t_last=$(python3 - "$js" <<'PY'
import json,sys
last=0
for l in open(sys.argv[1],errors="ignore"):
try: e=json.loads(l)
except: continue
if e.get("timestamp") and e.get("type") in ("user","assistant"):
last=e["timestamp"]
print(last)
PY
)
sib=$(find "${js%.jsonl}"/{tool-results,subagents} -type f -printf '%TFT%TT\n' 2>/dev/null | sort | tail -1)
[ -n "$sib" ] && [ "$sib" \> "$t_last" ] && echo "FROZEN: $js last-turn=$t_last sibling=$sib"
doneThis issue has 1 comment on GitHub. Read the full discussion on GitHub ↗