Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

deadman

ci

Dead man's switch for Claude Code: writes the handoff before your prompt cache dies.

Claude Code keeps your conversation in a 1-hour prompt cache. Reading the full context at minute 50 costs ~10% of the token price; at minute 61 it's full price again. So the moment you walk away from a long session is exactly the moment its state gets expensive to save.

deadman arms a switch every time Claude stops:

  • You come back → the next turn disarms it. Nothing happens, ever.
  • You stay away → at T-15m before cache death it pings your phone. At T-10m it wakes the idle session — one last cheap read of everything — and has Claude write a dated handoff to .claude/handoffs/, then stop.

Next time you start a session in that workspace, deadman offers the handoff back as context. Walk away mid-task, come back tomorrow, resume in minutes.

(The cache math is real money on API billing. On subscription plans you aren't billed per token — the win there is the loop itself: a phone warning before anything happens, and a written handoff waiting instead of a cold session.)

Install

Needs bash and jq. One file, no daemon.

curl -fsSL https://raw.githubusercontent.com/thevibeworks/deadman/main/bin/deadman -o /tmp/deadman
bash /tmp/deadman install        # or: install --project  (this repo only)
bash /tmp/deadman doctor         # everything wired?
bash /tmp/deadman drill          # 10-second live end-to-end self-test

install copies the script to ~/.claude/deadman/bin/deadman and wires three hook entries into settings.json (idempotent, with a backup): two Stop hooks (the "armed" announcement + the sleeper) and one SessionStart hook (the resume offer). Restart running Claude sessions — hooks load at session start.

From then on, every turn ends with:

deadman armed: auto-handoff in 50m, warn at 45m if idle (5f3759df; deadman status)

What a fire looks like

  1. 45m idle — your phone pings: "claude idle 45m: myproject — auto-handoff in 5m unless you return". Touch the session and nothing more happens.
  2. 50m idle — the session wakes itself, writes .claude/handoffs/2026-07-30-1036-5f3759df.md (goal, verified state, decisions, what not to repeat, next action), refreshes .claude/handoffs/latest.md, and stops. Newest 10 are kept.
  3. Next claude in that workspace: "an auto-handoff was written 2h ago — read it before starting."

Your repo-root HANDOFF.md (if you keep one by hand) is never touched — the robot writes only under .claude/handoffs/.

Commands

deadman install [--project]    wire the hooks into settings.json (idempotent)
deadman doctor                 verify dependencies, wiring, notifier
deadman drill                  ~10s live end-to-end self-test
deadman status [n]             armed sleepers on this host + last n events
deadman events [n]             tail the shared event log
deadman uninstall [--project]  remove hook entries (keeps binary + logs)

status shows what's armed right now — session, phase, seconds until fire, last tick. events is the append-only record of every decision the switch made (scheduled, tick, notified, fired, and every abort with its reason).

Notifications

Optional and pluggable — with no notifier the wake still fires; you just don't get the ping. Auto-detected in order:

  1. DEADMAN_NOTIFY_CMD — any command; gets TITLE BODY appended as args
  2. Bark via barkme.sh, when BARK_KEY is set
  3. ntfy when NTFY_TOPIC is set
  4. terminal-notifier (macOS)

deadman doctor tells you which one it picked.

Configuration

Environment variables always win; ~/.claude/deadman/config.env (written by install, all defaults commented) is for persistent overrides. Times are offsets from the 1h cache death:

variable default meaning
DEADMAN_AFTER_SEC 3000 fire at 50m idle (T-10m)
DEADMAN_LEAD_SEC 300 warn 5m before firing (T-15m)
DEADMAN_ANNOUNCE 1 0 = no "armed" line at each Stop
DEADMAN_SKILL (empty) delegate the wake to a skill you own
DEADMAN_NOTIFY_CMD (auto-detect) custom notifier command
DEADMAN_HANDOFF_DIR .claude/handoffs where dated handoffs land
DEADMAN_KEEP 10 dated handoffs kept per workspace
DEADMAN_TICK_SEC 30 gate re-check interval
DEADMAN_GRACE_SEC 5 late-flush tolerance (see below)

How it works

Every Stop, an asyncRewake hook forks a sleeper and kills the session's previous one — so the idle timer resets on any activity, and a sleeper that survives the full window is the proof you were away. It re-checks its gates every 30s and dies the moment any of them fail:

  • a newer sleeper owns the session (you came back)
  • the Claude process exited
  • a real user/assistant entry landed after schedule + grace
  • this activity position already fired (dedup)
  • a sibling session in the project went active (/clear, session switch)

The subtle part is the idle signal. The transcript file is not append-only while you're away: Claude writes housekeeping into it (titles, mode records, away-summaries), and the final message of a turn can flush to disk after the Stop hook already started. File size and mtime are lies. deadman reads the timestamp of the last real user/assistant entry and only counts it as activity if it's later than the sleeper's start plus a small grace window. Each of those rules exists because we shipped the naive version first and watched it fail; the test suite pins all of them.

State that involves PIDs stays host-local ($TMPDIR/deadman — PIDs don't cross container namespaces). The event log is shared and host-tagged, so deadman events shows every machine writing to the same ~/.claude.

Caveat: the wake rides on asyncRewake, an undocumented Claude Code hook field (≥ 2.1.220). If a future release removes it, deadman degrades to doing nothing — it can never break your session.

License

MIT

About

Dead man's switch for Claude Code: writes the handoff before your prompt cache dies

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages