Automatically resume an interrupted Claude Code session after a usage limit resets — unattended — and tell the next session what the background run did.
Claude Code has no built-in "continue when my limit resets" yet. Community tools do either auto-resume or session handoff; this does both, and adds the reliability pieces that make unattended resume actually finish work instead of stalling or churning:
- Survives reboot/sleep — a
launchdpoller, not a foreground loop. - Autonomous — resumes hands-off without freezing on permission prompts.
- Handoff ledger — the background run leaves a
HANDOFF.mdthe next foreground session reads, with a live-collision warning if a resume is still running. - Frontend collision gate — when you return after a limit hit, the frontend forces a handoff check before acting on your prompt (wait/take-over/kill if the backend is live; progress summary if it finished).
- Anti-churn — compact-first resume + a watchdog that kills a runaway that produces no output.
macOS only (uses
launchd+tmux). Linux (systemd) is on the roadmap.
- Claude Code hits a usage/rate limit → its native
StopFailurehook fires. - The hook records a job stamped with a resume time parsed from the limit message
(e.g. "resets 12:10am (Europe/London)") plus a buffer (or now + 5h as fallback),
and seeds
.claude/nightshift/HANDOFF.md— pure shell, so it works even while the API is limited. - A persistent
launchdagent (com.claude.nightshift.poller) sweeps every 5 minutes and at login, so a resume survives sleep, reboots, and missed windows. - When due, the poller resumes the same session in a detached, attachable
tmuxsession — autonomously (--permission-mode bypassPermissionsby default), compact-first ("Resume from summary" to avoid token churn), with the instruction delivered atomically viapaste-buffer. If Claude exits early (limit not reset yet) the job retries (up to 12 times, 30 min apart). - The resumed run is told to keep
HANDOFF.mdcurrent and is nudged every turn by aUserPromptSubmithook; aStophook blocks it from ending until it has written the ledger, and a watchdog kills it if it churns with no output. - Next time you open Claude in that project, a
SessionStarthook surfaces the handoff — what the backend did, or a warning that one is still running.
- macOS 12+
- Claude Code CLI (
claudein PATH) python3(built into macOS)tmux(brew install tmux) — required for unattended resume- Machine must stay awake — for overnight resumes, keep the laptop lid open (ideally on power). Closed-lid-on-battery resumes will not run: Apple's power management puts the machine into deep sleep, and there is no reliable workaround. If a resume misses its window, the frontend collision gate diagnoses "machine was asleep" so you know it was an environment limit, not a tool bug.
- (optional) Warp terminal — AppleScript fallback if
tmuxis unavailable
git clone https://github.com/Chrisabcde/claude-nightshift
cd claude-nightshift
./install.sh # deploys scripts, registers hooks, loads the pollerThe installer registers five hooks in ~/.claude/settings.json
(StopFailure, SessionStart, two UserPromptSubmit — one for the backend
ledger writer, one for the frontend collision gate — and Stop) and loads the
launchd poller. Re-run it any time to redeploy.
The hard part of background resume: when you next open Claude, the foreground has no idea what the background already did. The handoff ledger closes that gap.
- Backend → foreground.
StopFailureseedsHANDOFF.md; the resumed run keeps it current (## Done/## Next/## Blocked,status,last_update), driven by the resume instruction, a per-turnUserPromptSubmitnudge, and aStophook that won't let it stop with an empty ledger. - Foreground surfacing. A
SessionStarthook injects a compact, framed pointer (status + age + liveness), always as "a report to verify," never as live instructions. Silent in projects with no nightshift activity and suppressed for stale finished jobs — near-zero token cost when irrelevant. - Live-collision guard. If a resume is still running when you open a session,
you get a hard warning naming the
tmux attachcommand, so two Claudes don't edit the same files at once. - Frontend collision gate. A
UserPromptSubmithook forces the frontend to check the handoff before acting on your prompt. If the backend is live: a full menu (Wait / Take over / Kill) on the first prompt, then a terse guardrail on each subsequent prompt. If the backend finished: a progress summary once per run, then silent. Stale (>7 day) or already-reported runs are suppressed — zero token cost when irrelevant.
Single-writer (backend) / read-only (foreground), so no locking. The file is
git-ignored locally (a scoped .claude/nightshift/.gitignore).
The backend resumes from a compacted summary (NIGHTSHIFT_RESUME_MODE=summary,
default), not the full conversation transcript — startup token cost is bounded and
one-time.
A bloated resumed context can make the model burn tokens re-orienting instead of working. Two guards:
- Compact-first — resume from a summary (
NIGHTSHIFT_RESUME_MODE=summary, default) and a "limited budget — act, don't churn" instruction. - Churn watchdog — each poller sweep tracks a per-session progress marker
(transcript / handoff / file mtimes); a session with no output for >20 min
(
NIGHTSHIFT_CHURN_TIMEOUT) is killed and the handoff records why. Run on demand:~/.claude/nightshift/scripts/check-resume.sh --churn-check.
claude-schedule-resume # manually schedule a resume (now + 5h)
ls ~/.claude/nightshift/jobs/ # jobs (status inside each JSON)
cat <project>/.claude/nightshift/HANDOFF.md # what the backend did
tmux attach -t claude-nightshift-<job_id> # watch a live resume (Ctrl+B D to detach)
~/.claude/nightshift/scripts/check-resume.sh --reap # clean up stale background sessions| Variable | Default | Effect |
|---|---|---|
NIGHTSHIFT_MODEL |
sonnet |
Model the backend resumes with |
NIGHTSHIFT_PERMISSION_MODE |
bypassPermissions |
Backend autonomy. Default is fully hands-off (all prompts bypassed — an autonomous agent with no approval gates). Set acceptEdits or default for safer, attended resumes |
NIGHTSHIFT_RESUME_MODE |
summary |
Compact-first: resume from a summary, not the full transcript. Set full to keep the whole context |
NIGHTSHIFT_CHURN_TIMEOUT |
1200 |
Seconds a resume may run with no output before the churn watchdog kills it |
NIGHTSHIFT_ZOMBIE_MAX_AGE |
86400 |
Seconds before a stale resume session is reaped |
NIGHTSHIFT_MAX_ATTEMPTS |
12 |
Resume retries before a job is marked failed |
Note on autonomy:
bypassPermissionsruns the backend with all approval gates off — necessary so an unattended resume never freezes on a prompt, but it means the resumed agent can run anything in your project. SetacceptEditsif you want it to pause on non-edit prompts instead.
./test/test-hook.sh # StopFailure detection + handoff seeding
./test/test-schedule.sh # job stamping, reset-time parsing, poller loading
./test/test-resume.sh # dry-run resume: success / retry / finished-job guard
./test/test-handoff.sh # handoff surfacing, collision, anti-stale, autonomy, watchdogUses Claude Code's native StopFailure hook; the error field is matched against
rate_limit|overloaded|unknown. If a particular limit isn't auto-detected, run
claude-schedule-resume manually and check ~/.claude/nightshift/jobs/<id>.json for
the error value so the matcher can be tightened.
Reimplements (in zsh/python, ideas only) patterns proven by several MIT-licensed
projects — see CREDITS.md.
claude-nightshift status/ cancel command, desktop notifications- A resume/summary CLI flag if Claude Code exposes one (replacing the menu keystroke)
- Linux support via systemd timer + tmux
Add a top-level LICENSE before open-sourcing (MIT recommended — compatible with the
credited projects).