Claude Code runs npm run dev, waits, and waits, and waits. The dev server is up but stays alive forever — the agent thinks the command “has not finished yet” and the session locks up with a spinning indicator. Or Bash(curl https://flaky-api.com) hangs on a TCP read because the remote never closes the connection. Or a Python script enters an infinite loop and the model patiently waits hours for stdout. The session is alive but useless. Claude Code’s Bash tool has a default timeout, but many real-world commands either block longer than the default or expect to run forever, and the agent has no built-in way to distinguish “still working” from “permanently stuck”. The fix is to set explicit timeouts, use the right execution mode (background vs foreground), and add hang-detection patterns to your prompts.
Common causes
1. Long-running command run in foreground without a timeout
npm run dev, next dev, python -m http.server, tail -f — these are designed to run forever and only stop on Ctrl-C. Run in foreground inside a Bash tool call, they will time out at the tool’s default (often 2 minutes) and leave the agent confused.
How to spot it: The hanging command is a server or watcher. The terminal output shows “Server running on…” or similar, then silence.
2. Default 2-minute timeout was too short for the task
A real long-running task (test suite, large build, npm install of 800 packages) legitimately takes 5+ minutes. The tool times out, the agent thinks it failed, retries, eats more time.
How to spot it: Task is genuinely long but bounded. Tool fails with a timeout message, not a command error.
3. Network call to an unresponsive endpoint
curl, wget, git fetch to an endpoint that accepts the connection but never sends data. TCP keeps the socket open; the tool waits.
How to spot it: Command is network-bound. No progress output, no error. Often resolves if you kill and retry.
4. Command waits for stdin
Some commands read from stdin when not piped: rg --interactive, git commit without -m, REPL launches, anything calling read in shell. The tool has no terminal to read from.
How to spot it: Command is one that would prompt a human in a real terminal. No output, indefinite wait.
5. Process spawned a daemon that detached
docker compose up without -d, pnpm install that triggers a postinstall daemon, scripts that fork into the background but the parent process stays alive waiting on a pipe.
How to spot it: Initial output happens then hangs even though “the work seems done”. Often a child process is keeping the parent alive.
6. Hook is wrapping the command and blocking
A PreToolUse or PostToolUse hook that hangs blocks the tool call indefinitely. The tool itself ran instantly; the hook is stuck.
How to spot it: Tool call appears to start but no output appears for the actual command. Suspect a hook if you have any custom hooks installed.
Before you start
- Confirm whether you can see Claude Code in an actually-stuck state vs simply running a slow legitimate task.
- Note which exact command hung — the recovery differs for “server” vs “test suite” vs “network call”.
- Keep the session open if possible; killing it loses context that may identify the cause.
Information to collect
- The exact
Bashcommand that hung, including any wrapping. - Whether it printed any output before going silent.
- The default Bash timeout in your Claude Code version.
- Whether the same command runs and completes in a regular terminal.
- Any custom hooks in
~/.claude/settings.jsonor project settings. - Process tree at the time of hang:
ps -ef | grep <command>(in another terminal).
Step-by-step fix
Step 1: Identify whether the command should ever finish
Three categories, three different fixes:
| Category | Examples | Fix |
|---|---|---|
| Bounded (will end) | tests, builds, installs | Increase timeout |
| Unbounded server | dev server, watcher, daemon | Run in background |
| Stuck waiting | network hung, stdin read | Kill and avoid pattern |
Get this classification right before choosing a fix.
Step 2: For bounded long tasks, set an explicit timeout
Use the Bash tool’s timeout parameter if available, or wrap the command:
# Explicit timeout via tool parameter (preferred)
Bash(npm test, timeout=600000) # 10 minutes in ms
# Or wrap in shell timeout
Bash(timeout 600 npm test)
Set the timeout to 2-3x the expected runtime so legitimate slow runs do not get killed.
Step 3: For servers and watchers, use background execution
Run in background and capture output to a log:
# Start the dev server in background, log to file
Bash(npm run dev > /tmp/dev-server.log 2>&1 &)
# Wait briefly for startup
Bash(sleep 3 && curl -s localhost:3000 | head -5)
# Later, check progress
Bash(tail -20 /tmp/dev-server.log)
This frees the agent immediately and lets it inspect output later.
Step 4: Add a hang-detection prompt
Teach Claude to recognize hangs and recover. Add to CLAUDE.md or the session prompt:
For any Bash command, if no output for 60 seconds and the command is
not known to be intentionally long-running:
1. Stop and inform the user
2. Suggest killing the process and trying an alternative
3. Do not retry the same hanging command silently
For long tasks (tests, builds), always pass an explicit timeout to Bash.
For servers and watchers, always run in background with output to /tmp/.
The model honors this guidance and surfaces hangs early.
Step 5: Kill stuck processes cleanly
When stuck, recover without losing the session:
# In a separate terminal:
ps -ef | grep <command>
kill -TERM <pid>
# If TERM does not work:
kill -KILL <pid>
# Then in Claude Code, interrupt the tool call:
# Press Esc / Ctrl-C depending on your client
After killing, ask Claude to acknowledge the failure and pick an alternative path.
Step 6: Verify and prevent hangs from hooks
If you suspect a hook is hanging, temporarily disable it: mv ~/.claude/settings.json ~/.claude/settings.json.bak, restart Claude Code, retry the command. If it works without the hook file, the hook was the cause — restore and narrow its matcher so it does not run on unrelated commands.
Verify
- The previously hanging command now either completes within the timeout, runs in background and returns control, or is recognized as stuck and surfaced to you.
- Claude Code does not silently retry the hung command.
- Output of long-running servers is captured in
/tmp/log files that you can tail. - The session remains usable for other tasks while a server runs in background.
- The hang-detection guidance in CLAUDE.md causes Claude to bail out quickly when no output for 60 seconds.
Long-term prevention
- Always pass an explicit timeout to Bash for any command expected to take >30 seconds.
- Default to background execution for any command that runs a server, watcher, or daemon.
- Build a small library of safe wrappers in
package.jsonscripts that print progress and self-terminate. - Add the hang-detection prompt to CLAUDE.md so every session inherits it.
- Avoid commands that read stdin in agent context — pass arguments via flags instead.
- Keep hooks minimal and matcher-narrow so they cannot accidentally block unrelated work.
- For network calls, always include
--max-timeor equivalent timeout flags.
Common pitfalls
- Letting the agent retry the same hanging command 3 times in a row — wastes context and time, never recovers.
- Setting timeouts too tight (60 seconds) for legitimate long tasks — false hangs are as bad as real ones.
- Running
npm run devin foreground and being surprised when the agent locks up. - Forgetting to clean up background processes — eventually the laptop has 12 leaked dev servers.
- Using
tail -fin a tool call to “see live progress” — that just guarantees a hang.
FAQ
Q: What is the default Bash timeout in Claude Code?
A: Typically 2 minutes (120000 ms) for foreground execution. Pass an explicit timeout parameter to override it up to about 10 minutes.
Q: Can Claude Code see the background process is still running?
A: Not directly — once backgrounded, it is a normal OS process. Claude can check status by reading the log file or running ps / curl localhost:PORT.
Q: Should I always background long-running commands? A: Yes for servers and watchers. No for finite tasks (tests, builds) — those should run foreground with a large timeout so the agent sees the exit code.
Q: What if I need interactive input (password, confirmation)?
A: Avoid interactive commands in agent context. Use flags (--yes, --no-input) or pre-set credentials in env vars / config files. Pass git commit -m "msg" not git commit.