← Blog

2026-06-25

Recovering the Claude Code Sessions a Crash Ate

My Mac rebooted for a security update while I had three Claude Code sessions open — one deep in a migration, one mid-debug on a flaky test, one just chatting through an architecture decision. When it came back up, I ran claude --resume expecting to pick up where I left off. It showed me a list of finished sessions from days ago. The three I actually cared about, the ones that were open in terminal tabs five minutes before the reboot, weren’t there.

That’s the bug, and it’s not really a bug — it’s a category error I kept making. --resume is a session picker. The official docs are clear about what it lists: sessions are saved continuously to local transcript files “so you can return to one after exiting or running /clear.” The design case is a clean exit. A session still open in a terminal doesn’t have a tidy end state; it just stops writing when the process dies. Nobody told the picker “this one’s done,” so it never surfaces. The exact sessions you’re most likely to want back — because a reboot is rarely something you scheduled — are the ones the tool has no clean record of.

So the question becomes: how do you find something the tool never wrote down as findable? You don’t ask Claude Code. You go around it, to the filesystem and the terminal, and read the same crash-forensics tea leaves you’d read for any process that died without a clean shutdown.

Where the sessions actually live

Everything Claude Code knows about a conversation is on disk. Per the docs:

By default, transcripts are stored as JSONL at ~/.claude/projects/<project>/<session-id>.jsonl, where <project> is your working directory path with non-alphanumeric characters replaced by -. […] The entry format is internal to Claude Code and changes between versions, so scripts that parse these files directly can break on any release.

That warning matters for how claude-revive is built: it treats those files as presence signals, not a schema to parse. It cares that a transcript exists and when it was last written — not what’s inside a given line. That’s the part that stays stable across Claude Code releases.

Three sources, none of them the resume list

The reboot mtime-burst. Every running claude process flushes its transcript to disk on shutdown — including the shutdown from a reboot signal. So in the seconds before a reboot, every live transcript gets its mtime bumped, all clustered in a tight window. Scan ~/.claude/projects/*/ for files whose mtime falls in that burst just before boot time, and you’ve found every session that was actually running when the machine went down — no reliance on Claude Code ever calling them “closed.”

IDE terminal scrollback. If a tab was idle rather than actively streaming, VSCode and Cursor still persist its scrollback buffer across restarts. Somewhere in that buffer, from when you first launched it, is the line claude --resume <session-id> or the startup banner with the id in it. Grep the persisted buffers for that pattern and you recover sessions that never got near the mtime burst because they weren’t writing at reboot time — they were just sitting open.

A live registry, if you opt in. The first two sources are archaeology — inferring liveness after the fact. claude-revive init uses Claude Code’s own SessionStart/SessionEnd hooks (a SessionEnd hook, per the docs, “runs when a session terminates”) to keep a ledger going forward: every interactive session gets registered, and a clean exit gets tombstoned. This is the only source that can tell you a session was deliberately closed, which matters for the next part.

Union, then subtract the noise

None of these three sources is complete on its own, and none agrees with the others about what “recoverable” means, so claude-revive unions them and then works to earn back your trust in the result. A session found by two sources is still one session, not two. A session the registry tombstoned as cleanly closed gets dropped even if the mtime scan would have flagged it — you closed that tab on purpose, it shouldn’t come back.

Then it filters what’s left. Claude Code’s context-compaction step writes its own transcript artifacts that look, structurally, like sessions — they’re not; offering them back as “resume this” is just noise. Same for sessions that ran entirely in a scratch temp directory, or that never got a single real turn. None of that belongs in a crash-recovery picker, so it’s stripped before you see a list.

What’s left groups by project, shows any custom /rename title so you’re not guessing from the first line of a transcript, and drops you into a small TUI:

npx claude-revive          # picker over every recoverable session, grouped by project
claude-revive --here       # only this project's directory
claude-revive --active     # currently-live sessions — handy right before a planned restart
claude-revive --grep TERM  # narrow to sessions that mention TERM
claude-revive 900          # widen the reboot mtime window to 900s

d drops a session permanently — never offer it again — because a picker that keeps re-surfacing sessions you’ve already dismissed is worse than no picker. It also auto-refreshes, because resuming one of these in another terminal while the picker is open is a real scenario, not an edge case.

The part that actually closes the loop

A picker you have to remember to run is a picker you won’t run at 2am after a crash. claude-revive init --shell adds a block to your ~/.zshrc that snapshots live sessions and, within an hour of boot, auto-opens the picker the moment a restored IDE terminal comes back up. You don’t go looking for your lost sessions — they come looking for you, once, in the window where they’re still recoverable. CLAUDE_REVIVE_OFF=1 disables it per-shell; both settings.json and .zshrc are backed up (*.claude-revive.bak) before any edit, and init --uninstall reverses cleanly.

Honest limits

It’s macOS-only for now. The IDE-scrollback source in particular leans on how VSCode/Cursor persist terminal buffers on that platform; I haven’t built or tested the equivalent for Linux or Windows terminals. The mtime-burst forensics generalize, but the scrollback trick needs porting.

It’s also downstream of Claude Code’s own persistence settings. If you’ve set cleanupPeriodDays: 0 — which, as one issue documents, silently disables transcript writes entirely rather than just disabling cleanup — there’s nothing on disk to recover, and no forensics can conjure it back. The registry hooks are your only signal in that case.

claude --resume still does what it does well — it’s a fine picker for sessions that ended the normal way. claude-revive is for the other case: the one where nothing ended normally, and the record you need was never a record at all, just a side effect of something else flushing to disk on its way out.

One thing I keep going back and forth on: should recovering an unclean exit be the tool’s job at all, or is a session that died mid-thought something you should just let go? Where’s the line for you — do you want your crashed tabs back, or is a reboot a clean slate?

Repo: github.com/Livshitz/claude-revive · npm: claude-revive

References