A crashed Claude Code session for this project would not reopen — claude --resume <session> failed with an opaque Claude API 400 that can’t be cleared from the CLI. Root cause was a bloated, tool-block-unbalanced transcript. The fix is not to revive the session: reconstruct state from AGENT_HANDOFF.md + the historian and continue in a fresh session.
You cannot
/compactyour way outThe 400 fires before the session loads, so no in-session command (
/compact,/clear) can run. Don’t burn time trying to open the dead session — go straight to the recovery pattern below.
Symptoms
- A working Claude Code session crashes (abrupt, mid-tool-call).
claude --resume <session-id>returns a Claude API 400 with no actionable detail.- The 400 reproduces on every resume attempt; nothing in the CLI clears it.
Root Cause
Diagnosed by inspecting the session transcript directly:
~/.claude/projects/-Users-levander-coding-polymarket-fetch/<id>.jsonl
- 26 MB / 14,155 messages, already auto-compacted 3×.
- ~3,000 messages live since the last compaction checkpoint (last
isCompactSummaryat line 11,118) — that tail is the active replay window Claude Code rebuilds the API request from. - The replayed window is large enough on its own to exceed the model’s token ceiling.
- The abrupt crash also left the tool blocks unbalanced: 1,456
tool_usevs 1,481tool_result.
Either condition alone triggers the 400 (over-ceiling request, or malformed tool_use/tool_result pairing). Here both were present. Because the request is assembled and rejected before the interactive session is live, there is no CLI escape hatch.
Recovery Pattern (the takeaway)
Do NOT try to revive the giant session. Instead:
- Reconstruct working state from
AGENT_HANDOFF.md(the project’s cross-session continuity doc — this is exactly what it exists for) plus thehistoriansubagent for anything the handoff doesn’t cover. - Start a fresh Claude Code session and continue the work there.
- Re-verify any in-flight (uncommitted) work against the full CI gate before trusting it.
The handoff doc is the intended continuity mechanism; lean on it rather than fighting a corrupt transcript.
Diagnostic Commands
Run against the crashed session’s .jsonl. Set the path once:
SESSION=~/.claude/projects/-Users-levander-coding-polymarket-fetch/<id>.jsonl
# size + message count
wc -l "$SESSION"
du -h "$SESSION"
# find any single oversized message (length \t line-number, biggest first)
awk '{print length()"\t"NR}' "$SESSION" | sort -rn | head
# inspect tool-block pairing at the tail (counts must match)
grep -c '"type":"tool_use"' "$SESSION"
grep -c '"type":"tool_result"' "$SESSION"
jq -c 'select(.message.role) | {role: .message.role,
tu: [.message.content[]? | select(.type=="tool_use").id],
tr: [.message.content[]? | select(.type=="tool_result").tool_use_id]}' "$SESSION" | tail
# locate compaction checkpoints / size of the active replay window
grep -c '"isCompactSummary":true' "$SESSION"
grep -n '"isCompactSummary":true' "$SESSION" | tail -1 # last checkpoint = start of live window
# detect a truncated final write from the crash (errors if last line is invalid JSON)
tail -n1 "$SESSION" | jq -e .Interpreting the output:
du/wchuge (tens of MB, >10k messages) → the replay window is likely over the token ceiling.tool_usecount !=tool_resultcount → unbalanced blocks; this alone 400s.- Last
isCompactSummaryfar from EOF → large active window since the last compaction (the part that gets replayed). tail -n1 | jq -eerrors → the crash truncated the final write; the transcript is also structurally broken.
Outcome
Recovered cleanly. Continued in a fresh session, reconstructed state from AGENT_HANDOFF.md + historian, and verified the in-flight work — the uncommitted B1 supabase-upsert-retry fix in crates/pm-arb/src/validate.rs — green against the full CI gate (116 tests pass). No work lost.
Prevention
Keep
AGENT_HANDOFF.mdcurrent during long sessions. Long-running, heavily-compacted sessions are the ones at risk; the handoff doc is what makes a fresh start cheap when one dies.