Skip to content

Latest commit

 

History

19 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

claude-nightshift

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 launchd poller, not a foreground loop.
  • Autonomous — resumes hands-off without freezing on permission prompts.
  • Handoff ledger — the background run leaves a HANDOFF.md the 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.

How it works

  1. Claude Code hits a usage/rate limit → its native StopFailure hook fires.
  2. 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.
  3. A persistent launchd agent (com.claude.nightshift.poller) sweeps every 5 minutes and at login, so a resume survives sleep, reboots, and missed windows.
  4. When due, the poller resumes the same session in a detached, attachable tmux session — autonomously (--permission-mode bypassPermissions by default), compact-first ("Resume from summary" to avoid token churn), with the instruction delivered atomically via paste-buffer. If Claude exits early (limit not reset yet) the job retries (up to 12 times, 30 min apart).
  5. The resumed run is told to keep HANDOFF.md current and is nudged every turn by a UserPromptSubmit hook; a Stop hook blocks it from ending until it has written the ledger, and a watchdog kills it if it churns with no output.
  6. Next time you open Claude in that project, a SessionStart hook surfaces the handoff — what the backend did, or a warning that one is still running.

Requirements

  • macOS 12+
  • Claude Code CLI (claude in 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 tmux is unavailable

Install

git clone https://github.com/Chrisabcde/claude-nightshift
cd claude-nightshift
./install.sh           # deploys scripts, registers hooks, loads the poller

The 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.

Session handoff (foreground ↔ backend communication)

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. StopFailure seeds HANDOFF.md; the resumed run keeps it current (## Done / ## Next / ## Blocked, status, last_update), driven by the resume instruction, a per-turn UserPromptSubmit nudge, and a Stop hook that won't let it stop with an empty ledger.
  • Foreground surfacing. A SessionStart hook 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 attach command, so two Claudes don't edit the same files at once.
  • Frontend collision gate. A UserPromptSubmit hook 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.

Anti-churn

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.

Everyday commands

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

Configuration (env vars)

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: bypassPermissions runs 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. Set acceptEdits if you want it to pause on non-edit prompts instead.

Tests

./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, watchdog

Detection

Uses 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.

Credits

Reimplements (in zsh/python, ideas only) patterns proven by several MIT-licensed projects — see CREDITS.md.

Roadmap

  • 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

License

Add a top-level LICENSE before open-sourcing (MIT recommended — compatible with the credited projects).

About

Auto-resume an interrupted Claude Code session after a usage-limit reset, unattended, and tell the next session what the background run did. macOS (launchd + tmux).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages