claude-awake: keep your Mac awake (even lid-closed) while Claude Code is working — heartbeat hooks + pmset watchdog A developer created claude-awake, a pair of scripts that keep a Mac awake (even with the lid closed) while Claude Code is actively running tasks, using heartbeat hooks and a pmset watchdog. The daemon checks session state files and thermal throttling to decide whether to disable sleep, and restores sleep if the watchdog exits or the Mac overheats. | /bin/bash | | claude-awake-daemon.sh — the SINGLE authority on pmset disablesleep . | | | | Every POLL seconds it looks at two things and decides whether the Mac may sleep: | | 1. the per-session state files written by claude-awake-hook.sh | | a file is "active" only if it was refreshed within STALE MIN — a heartbeat, | | so a crashed/killed Claude can't pin sleep open forever | | 2. the thermal state CPU Scheduler Limit / CPU Speed Limit < 100 = throttling | | | | ≥1 active session AND not overheating → disablesleep 1 | | the lid can be closed and the Mac stays awake to finish the task | | otherwise all idle/closed, OR too hot → disablesleep 0 | | normal sleep — closing the lid sleeps it, so the battery doesn't drain; | | overheat → it's allowed to sleep & cool instead of cooking in clamshell | | | | Idle Claude instances open but not running a task keep NO active file, so they | | do NOT hold sleep open. Restores sleep if the watchdog itself exits. | | | | Run it via the LaunchAgent RunAtLoad + KeepAlive . Needs passwordless sudo for | | the two exact pmset commands — see the setup notes printed by the assistant. | | | | STATE DIR="$HOME/.claude/awake-state" | | LOG="$HOME/.claude/awake.log" | | POLL=20 seconds between checks | | STALE MIN=30 a session file not refreshed within this many minutes = stale → ignored | | COOL LIMIT=100 CPU Scheduler Limit / CPU Speed Limit below this = throttling hot | | | | mkdir -p "$STATE DIR" 2 /dev/null | | log { printf '%s %s\n' "$ date '+%F %T' " "$ " "$LOG"; } | | | | applied="" last value we actually pushed to pmset avoid redundant calls | | set sleep { $1: 1 = disable sleep stay awake , 0 = enable sleep allow sleep | | "$applied" = "$1" && return | | if sudo -n /usr/bin/pmset -a disablesleep "$1" 2 "$LOG"; then | | applied="$1" | | log "disablesleep=$1 $ "$1" = 1 && echo '→ staying awake lid-close OK ' || echo '→ sleep allowed' " | | else | | log "pmset FAILED — set up passwordless sudo for pmset see /etc/sudoers.d/claude-pmset " | | fi | | } | | | | overheating { 0 = hot/throttling, 1 = ok | | local therm field val | | therm="$ pmset -g therm 2 /dev/null " | | for field in CPU Scheduler Limit CPU Speed Limit; do | | val="$ printf '%s' "$therm" | sed -n "s/. $field :space: = :space: \ 0-9 \ . /\1/p" | head -1 " | | -n "$val" && "$val" -lt "$COOL LIMIT" && return 0 | | done | | return 1 | | } | | | | active count { find "$STATE DIR" -type f -mmin "-$STALE MIN" 2 /dev/null | wc -l | tr -d ' '; } | | | | Never leave sleep disabled if the watchdog dies / is restarted. | | trap 'sudo -n /usr/bin/pmset -a disablesleep 0 2 /dev/null; log "watchdog exit → sleep ENABLED"; exit 0' INT TERM | | | | log "watchdog up poll ${POLL}s, stale ${STALE MIN}m, cool<${COOL LIMIT} " | | while true; do | | if "$ active count " -gt 0 && overheating; then | | set sleep 1 | | else | | set sleep 0 | | fi | | find "$STATE DIR" -type f -mmin "+$STALE MIN" -delete 2 /dev/null reap stale markers | | sleep "$POLL" | | done |