You walked away for ten minutes. You came back to a frozen editor, a session marked "ended," and a TodoWrite list with three of eighteen items checked. The conversation transcript is still on disk under ~/.claude, but Claude Code will not resume it. Here is what those files actually contain, what you can recover by hand, and what a productized watchdog adds that grep cannot.
The "session ended with pending tasks" pattern
Pain point number four in every Claude Code complaint thread is the session that dies mid-work. The viral title for this on dev.to was direct:
Claude Code Lost My 4-Hour Session
Four hours of context. Eighteen open TodoWrite items. A half-applied refactor across thirty files. The editor's UI reports Session ended with no further explanation. The ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl file still exists on disk, but launching claude resume opens a fresh shell, not the conversation.
The complaint is specific. It is not "Claude Code crashed." It is "the work that Claude Code was doing on my behalf is no longer reachable from inside Claude Code." The transcript is preserved as a forensic artifact. The actionable state — what the agent was about to do, what tools it was about to call, what file it had just read — is not preserved as continuation state.
The pattern repeats often enough that the dev.to title became a meme. The fix the post recommends is a shell script that greps the JSONL and rebuilds context manually. That works. It also costs twenty minutes of your time per occurrence, and you lose the cached tool reads when you do it.
The four failure modes that produce it
Sessions can die in four distinct ways, each leaving the JSONL transcript in a different state:
1. Rate-limit pause that never resumes. The session hits a 429, Claude Code pauses, the Retry-After countdown elapses, and the resume never fires. The JSONL ends with a successful tool_result followed by silence. The session is technically alive — its file handle is still open inside Claude Code's process — but no further turns happen. Closing and reopening the editor presents the session as "ended."
2. Auto-compact eating context past a checkpoint. Claude Code's auto-compact runs when the context window approaches its limit. It summarizes earlier turns and replaces them with the summary. If the summarization happens mid-task and the new summary loses critical state — file paths, tool-call results, todo specifics — the session keeps running but the agent has lost track. The TodoWrite items may still exist in memory but the model cannot resume them because the context was rewritten.
3. Process crash. The Claude Code process dies. OOM-killer, terminal closed by accident, OS reboot, VS Code crash. The JSONL transcript is whatever was flushed to disk at the moment of death. The last record may be a complete tool_use block, a complete tool_result block, an incomplete JSON line where the writer was midway through a flush, or the empty file if the crash happened before any flushed turns.
4. Session-state file truncated mid-write. Rare but happens. Claude Code writes to the transcript as the session progresses. A crash during a write — or a disk-full event — can leave the last line malformed. The transcript is otherwise intact, but Claude Code's parser refuses to resume from a corrupt last record.
Each failure mode has a recognizable signature in the JSONL file:
# Rate-limit pause — ends on a successful tool_result, then nothing
tail -3 ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl
# Auto-compact — look for a "compact" event before the session lost coherence
grep '"type":"compact"' ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl
# Process crash — last record is a tool_use with no matching tool_result
jq -s '.[-2:]' ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl
# Truncated write — last line fails to parse as JSON
tail -1 ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl | jq .
Manual recovery — what you can actually do
The grep-and-replay workflow is what experienced Claude Code users converge on. The steps:
- Find the dead session's JSONL file under
~/.claude/projects/<encoded-cwd>/. The encoded-cwd is your working directory with/replaced by-, prefixed with a dash. A v3 project at/home/dev/projects/myappbecomes-home-dev-projects-myapp. - Read the last 5-10 records to figure out where the session died. Use
tail,jq, orless— whatever the muscle memory is. - Identify the last complete TodoWrite snapshot in the transcript. That is your starting point for the next session.
- Open a new Claude Code session and paste a summary of where the previous one left off, including the TodoWrite list and the next pending action.
- Re-prime any cached tool reads that mattered. Claude Code does not transfer cache across sessions, so the new session re-reads files at full token cost.
This workflow works. It costs ten to twenty minutes per occurrence and produces a session that is "the same conversation" only by approximation. The new session has lost the prompt cache, the parent agent's mental model of the codebase, and any in-flight tool calls. You are restarting with a summary, not resuming with state.
The deeper problem is that the workflow does not scale. A developer who hits this pattern twice a week is spending an hour a week on session recovery. A developer who runs autonomous loops overnight that may die unattended is spending the morning rebuilding context. Manual recovery is a workaround, not a solution.
Why automated recovery is structurally different
A watchdog process that monitors session lifecycle solves a different problem than the grep workflow. The grep workflow is forensic — it reconstructs what happened from artifacts left on disk after the death. The watchdog is preventive and structural — it captures actionable state at every step so that when the session dies, restart is one-click.
What a watchdog needs to capture, in order of importance:
- TodoWrite snapshots. Every PostToolUse event for the
TodoWritetool produces an updated todo list. A watchdog persists each snapshot keyed by session ID. On crash, the most recent snapshot is the restart point. - Pending tool calls. A
tool_useblock with no matchingtool_resultis an in-flight call. The watchdog records what the call was so the next session can re-issue it or skip it. - Compact events. Before auto-compact runs, fire a hook that snapshots the pre-compact state. After auto-compact runs, snapshot the post-compact state. The watchdog can offer either as a restart point.
- Rate-limit signals. When the session hits a 429, capture the
Retry-Afterand the active account. The restart logic knows when the throttle will release and which account to use. - Last live timestamp. A heartbeat — even a coarse one — distinguishes "session is paused mid-work" from "session died ten minutes ago."
The grep workflow can theoretically extract all five from the JSONL after the fact. The point of a watchdog is that the extraction happens proactively, the state is structured rather than forensic, and the restart UX is a one-click resume from the editor, not "open three terminal windows." The watchdog that does this is free to install with no credit card, and the 14-day Premium Trial — $0 today, cancel before day 15 — unlocks the full one-click recovery flow.
What a good watchdog needs to do
Beyond the five capture targets above, a productized watchdog needs:
- Cross-platform path handling. The
~/.claude/projects/layout uses an encoded-cwd path that differs by OS path conventions. Windows paths produce different encoding than POSIX. The watchdog has to resolve the correct directory for the active editor session regardless of OS. - Multi-session awareness. A developer may have three VS Code windows open with three Claude Code sessions running. The watchdog has to keep their states independent and present them in a UI that disambiguates.
- One-click resume from the editor. Not a CLI command. Not a shell script. A button in the VS Code sidebar that, when clicked, opens a new session pre-primed with the last known-good state from the dead session.
- Compact-event interception. Auto-compact runs faster than a developer can react. The watchdog has to capture pre-compact state via a hook that fires before the compact event completes, otherwise the snapshot is post-compact and the recovery is degraded.
- Graceful failure of the watchdog itself. If the watchdog crashes, the underlying Claude Code session must continue without interruption. The watchdog is observation, not in-band tooling.
The first version of this is the bash watchdog you can write in an hour. The productized version that lives in the editor and survives Claude Code's update cycle is a different scope of work.
Frequently asked questions
Where does Claude Code actually store its session transcripts?
Under ~/.claude/projects/<encoded-cwd>/, where <encoded-cwd> is your current working directory with / replaced by - and prefixed with a leading dash. Each session is a single <session-id>.jsonl file inside that directory. The format is newline-delimited JSON, one record per turn or tool call. The directory and file naming have been stable for several Claude Code versions.
If Claude Code crashes mid-edit, are my file changes preserved?
Yes, file edits go through the operating system's file API and are persisted regardless of Claude Code's state. The session that dies loses its conversation context, not the file changes the conversation produced. Recovery is about reconstructing what the next session needs to know, not undoing changes that already landed.
Why doesn't `claude resume` just work when a session has ended?
claude resume resumes the most recent session that Claude Code recognizes as resumable. A session that ended cleanly is resumable. A session that died via crash, rate-limit hang, or corrupted last record is presented as ended and resume opens a fresh one instead. The CLI does not currently expose a "resume any session from disk" mode that operates on the JSONL directly.
Can I write my own watchdog as a Stop hook?
A Stop hook fires when Claude Code exits a turn. It can capture state at every turn boundary, which covers most failure modes — rate-limit pauses, clean exits, compact events. It does not cover crashes that kill the process before the Stop hook runs. For full coverage you need an out-of-process watchdog that observes the JSONL file directly.
Does auto-compact destroy the session ID, or just the context?
The session ID is preserved. Auto-compact rewrites the context window's contents — replacing earlier turns with a summary — but the same JSONL file keeps growing under the same session ID. The forensic trail is intact. What is lost is the model's working memory of the pre-compact specifics.
What developers are reporting
The worst version of this is unrecoverable. GitHub issue #14472 reports that "sessions that grow large become permanently inaccessible. Users lose all conversation history and must start fresh" — a deadlock where the session is too big to load but must be loaded to compact. Durable checkpoints and reason-aware resume exist so "too big to open" stops meaning "gone."
Closing
The session that died with eighteen open todos is the worst experience Claude Code offers, and the only reason it is bearable is that the JSONL transcript is on disk for forensic recovery. Forensic is not the same as productized. The watchdog we ship captures actionable state at every turn boundary so that when the session does die, resuming is a click instead of an hour.