DEV Community

Cover image for Leaving Claude Code Running Overnight? 8 Settings to Check Before You Walk Away
Gabriel Anhaia
Gabriel Anhaia

Posted on AI-assisted

Leaving Claude Code Running Overnight? 8 Settings to Check Before You Walk Away


At 7 a.m. the terminal looks the way you left it at 11, apart from one line at the bottom: a permission prompt for a shell command, asked twenty minutes after you went to bed. You had handed the agent a refactor, added "run the tests when you're done", and walked away. It waited eight hours for an answer nobody was there to give.

That is one way an unattended run dies. The laptop going to sleep is another. So is a usage limit that resets on Thursday, or a run that reports "done" with the suite still red. Each of these has a setting you can change today. The eight below are checked against the Claude Code CLI reference and its changelog at 2.1.303, the newest release as I write this. Run claude -v first, because several need a recent version.

[ ] 1. caffeinate -i around the run
[ ] 2. idle compaction: on or off, on purpose
[ ] 3. retry watchdog, with a cap on usage-limit waits
[ ] 4. --max-turns and --max-budget-usd
[ ] 5. a permission mode nobody has to answer
[ ] 6. one worktree per task
[ ] 7. a Stop hook that sends it back while tests fail
[ ] 8. a way to hear how it ended
Enter fullscreen mode Exit fullscreen mode

Keep the run alive while you sleep

1. Stop the Mac from idle-sleeping

macOS ships caffeinate. With -i it creates an assertion that keeps the system from idle sleeping. Hand it a command and it holds that assertion only while the command runs:

caffeinate -i claude -p "Fix the flaky login test"
Enter fullscreen mode Exit fullscreen mode

For an interactive session, run caffeinate -i on its own in a second tab. It holds until you stop it or close that terminal. The -s flag blocks system sleep entirely, but the man page says that assertion is valid only on AC power, so plug the laptop in either way. Keep the lid open too: -i holds off idle sleep, not the sleep a MacBook goes into when you close the lid.

2. Decide what idle compaction does to a parked session

Since 2.1.299, Claude Code compacts a conversation of 200K tokens or more that has sat idle for about 54 minutes, before its one-hour prompt cache expires, so your next message doesn't reprocess the whole thing (changelog). That's a good trade for a session you come back to after lunch. For a run that finishes at 2 a.m. and then waits at the prompt, it means your first message at 7 continues from a summary, not the full conversation.

Choose on purpose. To keep the full conversation, turn it off in ~/.claude/settings.json:

{
  "idleCompaction": false
}
Enter fullscreen mode Exit fullscreen mode

Since 2.1.302 there is also an Idle compaction row in /config that does the same. If you want it on but later, CLAUDE_CODE_IDLE_COMPACT_MIN_TOKENS moves the threshold. The default is 200000, and anything under 100000 is raised to 100000 (env-var reference).

3. Tell it how long a usage limit may take

By default Claude Code retries a failed API request 10 times (env-var reference). For unattended sessions the same page points to CLAUDE_CODE_RETRY_WATCHDOG=1. It retries 429 and 529 capacity errors indefinitely, backing off up to 5 minutes between attempts, or until the limit resets when the response carries a reset time, "so a session that hits a usage limit waits out the remaining window." It still fails at once on a spend limit or exhausted usage credits.

Waiting out the window is right when the limit resets at 3 a.m. It is wrong when it resets in four days. Two newer variables limit the wait. CLAUDE_CODE_RETRY_WATCHDOG_MAX_WAIT_MS, added in 2.1.295, caps how long the watchdog waits out 429 and 529 errors, in plain-digit milliseconds. CLAUDE_CODE_RETRY_WATCHDOG_MAX_USAGE_LIMIT_WAIT_MS, added in 2.1.301, makes it end a request at a usage limit that resets too far away instead of waiting (changelog). The env-var reference has no row for that one yet and the changelog gives no unit, so the example below is my reading: milliseconds, going by the _MS suffix, and the value is how far off a reset may be before the watchdog gives up.

export CLAUDE_CODE_RETRY_WATCHDOG=1
# stop waiting out capacity errors after 30 minutes
export CLAUDE_CODE_RETRY_WATCHDOG_MAX_WAIT_MS=1800000
# give up on usage limits that reset > ~2 hours out
# (unit assumed to be ms, per the _MS suffix)
export CLAUDE_CODE_RETRY_WATCHDOG_MAX_USAGE_LIMIT_WAIT_MS=7200000
Enter fullscreen mode Exit fullscreen mode

Without the watchdog, a long outage uses up the default retries and you wake up to a dead session. With the watchdog and no cap, a weekly limit can keep the run waiting long after you're back at your desk.

Put a ceiling on the run

4. Cap turns and spend on headless runs

--max-turns limits agentic turns and exits with an error when the limit is reached. There is no limit by default. --max-budget-usd stops the run once estimated spend reaches the amount. It checks against a client-side estimate that can differ from your bill, and spend can pass the cap, so leave headroom (CLI reference). Both are print-mode only: they apply to claude -p, not to an interactive session.

If you run a mod that submits a prompt at the end of every turn, be on 2.1.299 or later. Before that release, such a mod could keep a headless run going past both caps (changelog), and you pay for every extra turn.

5. Choose a permission mode nobody has to answer

The scene at the top is a session in Manual mode: it reaches the first command that needs approval and waits. For an interactive session, start it with claude --permission-mode acceptEdits and add the commands it will need to permissions.allow in .claude/settings.json before you leave. For -p runs the headless docs say a run where nothing sets a permission mode takes the built-in starting mode, "which can be auto, so pass the one you want." The options they list:

  • acceptEdits: writes files without asking and auto-approves common filesystem commands such as mkdir, touch, mv and cp. Other shell commands still need an --allowedTools entry.
  • dontAsk: denies every call that would otherwise prompt.
  • auto: a classifier reviews most actions instead of you.

Add --permission-prompts none (2.1.259 or later) and anything that would prompt is denied, Claude is told nobody can approve it and not to retry, and the run carries on. Put together with items 1 and 4:

TASK="Fix the flaky login test. Run npm test until it passes."
caffeinate -i claude -p "$TASK" \
  --permission-mode acceptEdits \
  --allowedTools "Bash(npm test *)" "Bash(git diff *)" \
  --permission-prompts none \
  --max-turns 40 \
  --max-budget-usd 5.00 \
  --output-format json > overnight.json
Enter fullscreen mode Exit fullscreen mode

--dangerously-skip-permissions skips the prompts altogether. I'd keep that for a container you can throw away, and read what it does and does not skip first.

Keep the night's work reviewable

6. One worktree per task

claude -w fix-login starts Claude in its own git worktree at <repo>/.claude/worktrees/fix-login (CLI reference). Plain git does the same with git worktree add:

# from your main checkout, ~/code/app
git worktree add -b agent/fix-login ../fix-login
cd ../fix-login && npm ci && cp ../app/.env .
Enter fullscreen mode Exit fullscreen mode

Without one, the agent edits the checkout you'll open in the morning, mixed in with your own uncommitted work, and a second agent in the same folder writes over the first. In a worktree, the night's work ends up on a single branch: git diff main...agent/fix-login shows what changed, and deleting the branch throws it away. A new worktree holds only tracked files, which is why the second line installs dependencies and copies the .env. Do that before you leave, or the first test run fails on something that isn't the agent's fault.

7. Send it back while the tests fail

"Run the tests when you're done" is a request. A Stop hook is a rule. Claude Code runs Stop hooks whenever Claude finishes responding, and a command hook that exits with code 2 there "prevents Claude from stopping, continues the conversation" (hooks reference):

#!/bin/bash
# .claude/hooks/gate.sh: no stopping while tests fail
if ! npm test >/dev/null 2>&1; then
  echo "npm test is failing. Fix it before you stop." >&2
  exit 2
fi
Enter fullscreen mode Exit fullscreen mode

Register it in .claude/settings.json and make it executable with chmod +x:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/gate.sh"
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

The hooks guide version of this pattern reads stop_hook_active from the hook input and exits early when it is true, which sends Claude back only once. For an overnight run you want more than one try, so this script leaves that check out. It is still bounded: Claude Code overrides a Stop hook after it blocks eight times in a row with no tool call from Claude in between, so an agent that keeps fixing things keeps getting sent back, and one that is stuck gets let go. CLAUDE_CODE_STOP_HOOK_BLOCK_CAP raises that cap (env-var reference). One more reason item 3 matters: Stop hooks don't fire on API errors, which fire StopFailure instead.

Find out how it ended

8. A notification, and a result file

For interactive sessions, a Notification hook fires when Claude is waiting for input or permission. A small script keeps the JSON short; make it executable with chmod +x:

#!/bin/bash
# ~/.claude/notify.sh
MSG="${1:-Claude Code is waiting on you}"
osascript -e "display notification \"$MSG\" with title \"Claude\""
Enter fullscreen mode Exit fullscreen mode
{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          { "type": "command", "command": "$HOME/.claude/notify.sh" }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

On Linux, swap the last line of the script for notify-send 'Claude Code' "$MSG". Two catches from the hooks guide: osascript fails silently if Script Editor doesn't have notification permission, so run osascript -e 'display notification "test"' once and then turn on Allow Notifications for Script Editor in System Settings > Notifications. And notify-send needs a notification daemon, which headless servers, SSH sessions and most containers don't have.

For the -p run from item 5, end the command with ; ~/.claude/notify.sh "Run over" and read the JSON in the morning. The headless docs put the text in result and the estimated cost in total_cost_usd; jq pulls both out:

jq -r '.result' overnight.json
jq '.total_cost_usd' overnight.json
Enter fullscreen mode Exit fullscreen mode

Where Hermes IDE fits

I build Hermes IDE, so weigh this section with that in mind. It's a desktop app that runs Claude Code, Codex and other CLI agents, each in its own real terminal, and it isn't affiliated with Anthropic or any agent vendor. Because each agent runs its own interface, your settings.json (item 2) and the hooks from items 7 and 8 apply inside it unchanged. Variables you export in another terminal tab may not reach an agent the app starts, so put the item 3 variables in the env block of settings.json, where every session picks them up.

It takes work off two items. For item 6, ⌘N (Ctrl+Shift+N on Windows and Linux) starts each task on its own hermes/ branch in its own worktree, with the task as the agent's first prompt, and the approval mode is a chip on the same sheet. Its per-repo recipe file, .hermes/worktree.toml, can install dependencies and copy git-ignored files like .env into each new worktree, and takes a done_when list of checks. Hermes asks before it runs a recipe's setup commands, so start the task and approve them before you leave.

The Hermes New task sheet with a refund-endpoint task typed in, chips for Claude Code, the orders-api project, a new hermes/ worktree, Accept edits, model and effort, and a preview of the claude command it will run
The ⌘N New task sheet: Claude Code, the orders-api project, a new hermes/ worktree, Accept edits, model and effort, and the claude command Hermes will run.

For item 8, Hermes sends a system notification when an agent is blocked on you or done, never for the session you're already looking at. Every session shows one status: working, needs approval, asked you, done, error or idle.

One failure none of the settings cover is closing the app itself. On macOS and Linux, agents keep working while Hermes quits, updates or crashes, and you reattach where they left off. If an agent hit its usage limit overnight, you can continue the task in another agent in the morning.

Two agent sessions side by side, Codex and Claude Code, each in its own hermes/ worktree, with the sessions sidebar showing status, context use, cost and branch
Codex and Claude Code side by side, each in its own hermes/ worktree, with status, context use, cost and branch per session in the sidebar.

None of this replaces item 1: an agent only works while the machine is awake. Hermes is free, source-available under BSL 1.1, and runs on macOS, Windows and Linux. Download it or read the code on GitHub.


If this was useful

Most of this list is guardrails for an agent nobody is watching: a turn cap, a budget, a permission boundary, a test gate at the point where it wants to stop. The AI Agents Pocket Guide is my short book on the patterns underneath those, for when you build agents of your own. It's one of the Pocket Guides for Developers, 8 short books on agents, RAG, prompts, observability, events, databases and system design: the whole series.

AI Agents Pocket Guide: Patterns for Building Autonomous Systems with LLMs

Top comments (0)