The problem
Last time I built claude-sessions: one picker to resume any Claude Code session across projects. But a machine crash takes more than Claude with it — it takes your whole terminal layout. Which tabs were open, which directory each one was in, which ones had a Claude session running.
macOS window restoration exists, but it fails this job twice:
- It restores windows, not processes — your
claudesessions don’t come back. - It can’t tell a crash from a quit — close your terminal on purpose for a fresh start, and it cheerfully brings back all the old tabs anyway.
The solution
A snapshot daemon plus an explicit restore — no auto-restore, ever.
Every 5 minutes, a launchd job walks every iTerm window and tab via AppleScript, and for each tab resolves the tty → the processes on it (ps) → the working directory (lsof -d cwd) → and whether a claude process is running there, including which session: from its --resume argument, from the transcript file it holds open, or from the newest transcript for that directory. The result is a small JSON file.
Snapshots accumulate as history — the last 100 distinct states, consecutive duplicates skipped, and an empty terminal never overwrites anything.
After a crash:
claude-sessions --restore-crash # newest snapshot
claude-sessions --restore-pick # or: fzf through history, pick the right one
Enter fullscreen mode Exit fullscreen mode
Every tab comes back — as tabs of the window you’re standing in — cd‘d to its old directory, with claude --resume <session-id> rerun wherever Claude was live. Intentional quit? Just don’t run it. You are the crash detector; that’s the feature.
Three gotchas that cost me an evening
-
launchd can’t read iCloud paths. If your script lives in iCloud Drive (mine syncs via
~/.claude/bin→ iCloud), the timer dies withOperation not permitted— macOS TCC silently denies background jobs access to~/Library/Mobile Documents. Point the plist at a local copy. - A single snapshot file is a footgun. After a crash you reopen a near-empty terminal, the timer fires, and your pre-crash snapshot is gone — replaced by three sad tabs. That’s why history, not one file.
- Don’t restore the original window grouping. My first version faithfully recreated every window — including a junk window the snapshot had captured. Flattening everything into tabs of the current window is what you actually want.
Script, launchd template, and docs:
claude-sessions
Find and resume any Claude Code session from any project — and rebuild your whole terminal after a reboot — from one fzf picker.
What it does
-
Cross-project picker.
claude --resumeonly lists sessions for the directory you run it from.claude-sessionslists every session from every project in one picker, newest first. Enter opens the highlighted session in a new iTerm tab (or tmux window) runningclaude --resume <id>in the right directory — and the picker stays open for the next one. - Crash recovery. A timer snapshots your full iTerm layout every 5 minutes — every window/tab, its working directory, and the Claude session running inside it After a reboot (or an unattended OS update), one command rebuilds the whole set.
Why. Recovering by hand means remembering every repo you had open, cd-ing into
each, and running claude --resume there — once per session. This collapses…
답글 남기기