Why Claude Code Hangs (and How to Stop It)
A practical guide to why Claude Code sessions get stuck in headless and scheduled runs — and the techniques that actually prevent it.

If you've ever run Claude Code as a background job and returned to find it still going six hours later — output frozen, process alive, doing nothing — you've hit the silence problem. It's one of the most consistent pain points for developers who move from interactive Claude Code sessions to unattended, scheduled runs.
This guide covers why it happens, what the failure modes look like, and the practical steps to prevent it.
Why Claude Code Gets Stuck
Claude Code is designed for interactive use. In a normal terminal session, you're watching the output and can intervene when something goes wrong. When you run it headlessly, that feedback loop disappears — and several things can go wrong without you knowing.
Interactive prompts with no one there to answer them. Claude Code sometimes pauses and asks for confirmation before destructive operations: deleting files, overwriting code, running unfamiliar commands. In an interactive session, you answer. In a headless session, the process just waits. Indefinitely.
Build processes that never exit. When Claude Code runs a build command or test suite that hangs — perhaps a long integration test waiting on an external service, a dev server that starts but doesn't return control — it'll wait for that subprocess to complete. If the subprocess never completes, neither does the Claude Code session.
Loop conditions on ambiguous tasks. Give Claude Code a goal it can't fully satisfy — "make this code production-ready" — and it can get stuck iterating. Each attempt slightly changes something, the next iteration notices something else to fix, and the session accumulates cost without converging. This isn't really hanging in the technical sense, but from a practical standpoint it's the same problem: hours pass and you're no better off.
Network stalls and API timeouts. If Claude Code is making API calls as part of a task and one of those calls stalls rather than returning an error, the process can appear to be working when it's actually waiting on a connection that will never complete.
What Hanging Actually Costs
The silence problem isn't just inconvenient. It has a direct financial dimension.
Claude Code bills per token. A session that runs overnight and produces nothing useful still costs you whatever tokens were used during that silent period. A modestly complex overnight job runs £10–30 in API costs under normal conditions; a session that hangs for eight hours while looping on a stuck task can cost twice that, or more.
There's also the time cost. If you scheduled a job to run overnight and it got stuck, you've lost the automation window entirely. Whatever it was meant to do, it didn't. And you might not find out until the next morning.
Prevention: Setting Hard Boundaries
The most reliable way to prevent runaway sessions is to set hard boundaries before they start.
Use --max-turns
The --max-turns flag limits how many conversation turns Claude Code can make in a session. It doesn't prevent genuine progress, but it does cap the damage if Claude Code starts looping:
claude -p "Refactor the auth module to use the new token format. Stop after 3 turns if you can't make clear progress." --max-turns 15 --project /Users/you/myappA reasonable default for overnight jobs is 10–20 turns, depending on complexity. Adjust down for simpler tasks.
Use timeout Wrappers
The Unix timeout command kills a process after a specified duration regardless of what it's doing:
timeout 3600 claude -p "Your goal here" --project /path/to/projectThat runs Claude Code for at most one hour, then terminates it. If the job normally completes in 20 minutes, a one-hour limit is a sensible safety net.
The downside is bluntness — timeout just kills the process, which means any in-progress work is abandoned mid-stream. If you're doing file writes, you might leave things in a partially modified state. For read-heavy or idempotent jobs, this is fine. For write-heavy operations, it warrants more care.
Write Goals That Have a Defined End State
Open-ended goals are the leading cause of loop conditions. If Claude Code can't tell whether it's finished, it'll keep going.
Compare:
Vague: "Improve the test coverage."
Specific: "Add unit tests for the three untested functions in src/api/auth.ts. Stop when all three have at least one test that passes."
The second goal has a clear completion condition. Claude Code can evaluate whether it's done. The first doesn't — there's always another test that could be written.
When you're writing prompts for unattended runs, finish every goal with a sentence that defines what done looks like. Literally: "When done, commit the changes and exit."
Detection: Knowing When Something Has Stalled
Even with good prevention, sessions can still hang. Detection is the other half of the problem.
Monitor Output with a Wrapper Script
The most basic detection is checking whether output has appeared recently:
#!/bin/bash
LOG="$HOME/claude-logs/job-$(date +%Y%m%d-%H%M).log"
SILENCE_TIMEOUT=600 # 10 minutes
claude -p "Your goal" --project /path/to/project > "$LOG" 2>&1 &
CLAUDE_PID=$!
while kill -0 $CLAUDE_PID 2>/dev/null; do
LAST_OUTPUT=$(stat -f %m "$LOG" 2>/dev/null || echo 0)
NOW=$(date +%s)
if (( NOW - LAST_OUTPUT > SILENCE_TIMEOUT )); then
echo "Silence detected after ${SILENCE_TIMEOUT}s — killing process"
kill $CLAUDE_PID
break
fi
sleep 30
doneThis isn't polished, but it works. The process checks every 30 seconds whether the log file has been written to recently. If it hasn't been touched in 10 minutes, it kills Claude Code.
Use a Purpose-Built Tool
Writing your own silence detection is workable but tedious. OpenHelm handles this as a built-in feature — it monitors Claude Code's output stream and stops runs that go silent for more than 10 minutes. You get a timestamped record of when the silence started and what was last output, which is much more useful for debugging than a log file entry saying "killed."
The difference matters in practice. When a job fails via silence detection, you want to know what it was doing when it stopped so you can fix the goal or the environment. A blunt kill gives you less to work with.
A Realistic Overnight Job Setup
Putting it together, here's what a sensible overnight job configuration looks like:
#!/bin/bash
# nightly-deps.sh
# Update and test all dependencies for the main project
LOG="$HOME/claude-logs/deps-$(date +%Y%m%d).log"
mkdir -p "$(dirname "$LOG")"
timeout 7200 claude -p "Update all dependencies in package.json to their latest minor versions. Run the test suite after updating. If tests fail, roll back to the last known working version and document which package caused the failure in UPGRADE_NOTES.md. Stop after 20 turns or 2 hours, whichever comes first." --max-turns 20 --project /Users/you/myapp >> "$LOG" 2>&1What this setup does:
- Hard two-hour time limit via
timeout - Turn limit via
--max-turns - A goal with a clearly defined completion state
- A fallback action for when things go wrong (roll back + document)
- Logging for post-run review
This won't prevent every possible failure mode, but it catches the most common ones and limits the blast radius when something does go wrong.
FAQ
Does Claude Code have a built-in timeout?
Not as a simple CLI flag. You can set --max-turns to limit conversation length, but there's no flag for wall-clock time. You need to wrap the command in timeout or a monitoring script for time-based stopping.
Will killing Claude Code mid-task corrupt my files?
Possibly, for in-progress writes. Claude Code typically works atomically on individual files — it writes a complete file, not partial chunks — but if it's in the middle of a sequence of changes when you kill it, the codebase may be in an intermediate state. Running git status and git diff after a forced kill will show you what changed.
What's the best silence threshold to use?
Ten minutes is a reasonable default for most development tasks. Some operations — long test suites, large dependency installs, Docker builds — legitimately take more than ten minutes without producing output. If your job regularly involves slow operations, raise the threshold or structure the goal so Claude Code produces progress output more frequently.
Can I get notified when Claude Code hangs rather than just killing it?
Yes, with some scripting effort. The shell wrapper above can be extended to send a notification via osascript (macOS notification), curl to a Slack webhook, or any other notification method before killing the process. OpenHelm handles this natively — it sends a notification and records the silence event in the run history.
---
Running Claude Code unattended is genuinely useful. The silence problem is real, but it's solvable with the right setup. The techniques above handle the most common failure modes; a proper scheduling tool handles them more reliably if you're running multiple jobs or need structured run history.
See also: How to Schedule Claude Code Jobs, Claude Code Background Jobs vs /loop
More from the blog
OpenHelm vs runCLAUDErun: Which Claude Code Scheduler Is Right for You?
A direct comparison of the two most popular Claude Code schedulers — how each works, what each costs, and which fits your workflow.
Claude Code vs Cursor Pro: Real Developer Cost Comparison
An honest look at what developers actually spend on Claude Code, Cursor Pro, and GitHub Copilot — and how to get the most from each.