One script that prevents Claude Code from accidentally ending a session -- enabling true overnight autonomous work.
The agent said "continuing overnight work" but the loop ended because no tool call was made. This hook fixes that.
Claude Code's agentic loop ends when the assistant produces a response with no tool calls. An agent can intend to keep working but phrase its response as text ("I'll continue overnight") instead of making a tool call -- and the session silently dies.
The human wakes up to find nothing happened.
A Stop hook that fires every time Claude tries to end its turn. It:
- Generates a random nonce and blocks the stop
- Reminds Claude with a 6-point checklist (see below)
- Requires explicit confirmation -- Claude must include
STOP_CONFIRMED_<nonce>to actually stop - If Claude has more work, it just continues with a tool call -- no confirmation needed
The nonce prevents Claude from pre-generating the confirmation string. It must genuinely decide to stop after seeing the reminder.
When the hook fires, Claude is reminded to:
- Review task list -- any incomplete tasks?
- Review user messages -- did you miss anything?
- Continue overnight work -- make a tool call if work is queued
- Clean up background commands -- terminate any that are stuck, legacy, or unused
- Consider stretch goals -- if the main work is done, try bonus improvements on an experimental branch or separate folder. The criteria: the main work and the homelab must not be jeopardized. If the stretch succeeds, switch back to main. If it fails, keep the artifact and explain what went wrong in the sign-off.
- Confirm stop with nonce -- or send the nonce even with pending work if a bounded-time background command will finish it (spawn one instead of hot-grinding)
mkdir -p ~/.claude/hooks
cp stop-gate.sh ~/.claude/hooks/stop-gate.sh
chmod +x ~/.claude/hooks/stop-gate.shAdd this to ~/.claude/settings.json (or project-level):
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash /Users/YOUR_USERNAME/.claude/hooks/stop-gate.sh",
"timeout": 10
}
]
}
]
}
}Exit and re-enter for the hook to take effect.
Claude tries to stop
-> stop-gate.sh runs
-> Generates nonce (e.g., 759bee73b33e)
-> Returns: {"decision": "block", "reason": "...STOP_CONFIRMED_759bee73b33e..."}
-> Claude sees the checklist and either:
a) Continues working (makes a tool call) -- loop stays alive
b) Confirms stop with STOP_CONFIRMED_759bee73b33e -- session ends cleanly
c) Sends nonce + spawns a bounded-time background command for remaining work
- claude-code-time-awareness -- agent knows what time it is (informational). Stop gate is the enforcement.
- Together: agent sees it's 2 AM with 8 hours of compute left -> stop gate catches premature exit -> agent keeps working.
jq(for JSON output)xxd(for nonce generation, comes with most systems)
MIT