Keep your Mac awake for exactly as long as Claude Code is actively working — no more, no less — even across several concurrent sessions.
Claude Code can run for a while unattended (long tool calls, big builds), and
by default your Mac will happily go to sleep mid-task if you walk away. These
two hooks start a shared caffeinate -is (prevent idle + system sleep) on the
first prompt of a session, and stop it once every session that asked for it
has ended — refcounted, so two Claude Code windows open at once don't race to
kill each other's caffeinate.
caffeinate-start.shruns on UserPromptSubmit. It registers the current session (keyed by Claude'ssession_id) in/tmp/.claude-caffeinate/, prunes any sessions whose process has died, and startscaffeinate -isonly if nothing is running yet.caffeinate-stop.shruns on Stop and SessionEnd. It unregisters the session and kills the sharedcaffeinateonly when the session count drops to zero.- Both use an atomic
mkdir-based lock so concurrent hook invocations (from parallel sessions) never corrupt the session registry. - If the
tampCLI (tamp.kybernaut.cz) is installed, both scripts also mirror the shared session into Tamp's own state file at the two moments they actually start/stop it — so Tamp's menu bar andtamp statusshow it as their own "On" session, not just a generic external detection. This is a no-op if Tamp isn't installed; neither script has a hard dependency on it.
- Save both scripts to
~/.claude/hooks/andchmod +xthem. - Merge the
hooksblock fromsettings.jsoninto your~/.claude/settings.json(or~/.claude/settings.local.json). - Requires
jq(brew install jq) to parse the hook's JSON payload.
This was also the exact blind spot that led me to build
Tamp in the first place — a macOS menu bar
app + CLI that notices any external caffeinate process and shows
"caffeinated by another app" instead of pretending the Mac is free to sleep.