← Learnings

Snapshot Your Terminal, Restore It After a Crash

macOS window restoration restores windows, not processes — after a crash your tabs come back empty, your Claude Code sessions don't come back at all, and an on-purpose quit brings the old tabs back anyway. The fix is one script on a five-minute timer, and one command afterwards.

Snapshot every tab on a timer

Every five minutes a launchd job walks every iTerm window and tab with AppleScript. For each tab it resolves the tty, the processes on it with ps, the working directory with lsof -d cwd, and whether a claude process is live there. The result is a small JSON file.

~/.CLAUDE/TERMINAL-SNAPSHOTS/1787374087.JSONJSON
{
  "takenAt": 1787374087,
  "tabs": [
    { "window": "1856", "cwd": "/Users/dg", "claudeSession": null },
    { "window": "1856", "cwd": "/Users/dg/GitHub/Wildr/wildr-server", "claudeSession": null }
  ]
}
FIG 1 · ONE SNAPSHOT — TWO TABS, NEITHER RUNNING CLAUDE AT THE TIME

When a tab does have Claude live, claudeSession carries the session id — read from the --resume argument the process was started with, from the transcript file it holds open, or failing both, from the newest transcript for that directory.

Keep the last hundred states

A single snapshot file is a footgun: after a crash you reopen a near-empty terminal, the timer fires, and the pre-crash state is gone — replaced by one sad tab. So snapshots accumulate as history — the last 100 distinct states, consecutive duplicates skipped, and an empty terminal never overwrites anything.

zsh · macos
$ claude-sessions --snapshot
snapshot: unchanged (1 tab(s)); keeping /Users/dg/.claude/terminal-snapshots/1787424285.json
FIG 2 · A DUPLICATE STATE WRITES NOTHING

The file you restore from is the one from before the crash, not whatever the timer wrote after it.

Rebuild after a crash

Two commands. The first takes the newest snapshot; the second opens the history in fzf so you can pick the state you actually want back.

zsh · macos
$ claude-sessions --restore-crash   # newest snapshot
$ claude-sessions --restore-pick    # fzf through history, pick one
FIG 3 · THE WHOLE RECOVERY

Every tab comes back as a tab of the window you are standing in — cd'd to its old directory, with claude --resume <session-id> rerun wherever a session was live. The flattening is deliberate: the first version faithfully recreated the original window grouping, junk window included. Tabs of the current window are what you actually want.

An intentional quit needs nothing: just don't run it. There is no auto-restore — you are the crash detector, and that is the feature.

Install it

claude-sessions --install copies the script to ~/.local/bin, wires up PATH and a cs alias, and loads the snapshot timer.

~/LIBRARY/LAUNCHAGENTS/COM.CLAUDE.TERMINAL-SNAPSHOT.PLISTXML
<key>ProgramArguments</key>
<array><string>/Users/dg/.local/bin/claude-sessions</string><string>--snapshot</string></array>
<key>StartInterval</key><integer>300</integer>
<key>RunAtLoad</key><false/>
FIG 4 · THE TIMER, POINTED AT A LOCAL COPY ON PURPOSE

The local path is load-bearing. If the script lives in iCloud Drive, the timer dies with Operation not permitted before running a line — macOS silently denies TCC-protected folders like ~/Library/Mobile Documents to background jobs. That one cost an evening.

Nothing about the way you quit changes — close the terminal on purpose and no ghost tabs return. The restore runs only on the morning you actually need it.

TOOL: CLAUDE-SESSIONS ↗