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.
{
"takenAt": 1787374087,
"tabs": [
{ "window": "1856", "cwd": "/Users/dg", "claudeSession": null },
{ "window": "1856", "cwd": "/Users/dg/GitHub/Wildr/wildr-server", "claudeSession": null }
]
}
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.
$ claude-sessions --snapshot snapshot: unchanged (1 tab(s)); keeping /Users/dg/.claude/terminal-snapshots/1787424285.json
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.
$ claude-sessions --restore-crash # newest snapshot $ claude-sessions --restore-pick # fzf through history, pick one
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.
<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/>
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.