Claude Code runs npm run dev, then waits, and waits, and waits. The dev server is up and stays alive forever, so the agent thinks the command “has not finished yet” and the session sits on a spinning indicator. Or Bash(curl https://flaky-api.com) hangs on a TCP read because the remote accepts the connection but never sends data. Or a script enters an infinite loop and the model patiently waits for stdout that will never come. The session is alive but useless.
Fastest fix: if the command is a server, watcher, or anything meant to run indefinitely, don’t run it in the foreground. Either press Ctrl-b while it’s running to move it to the background, or tell Claude to run it with run_in_background: true. Claude gets control back immediately and can read the captured log later. If it’s a finite-but-slow task (a test suite, a big build), pass an explicit timeout in the Bash call instead. The rest of this guide covers how to tell which bucket you’re in and how to recover a shell that’s already wedged.
How the Bash tool’s timeout actually works (June 2026)
Per the official Claude Code tools reference, each Bash command is bounded by two limits:
- Timeout: 2 minutes (120000 ms) by default. Claude can request up to 10 minutes per command by passing the
timeoutparameter (in milliseconds). You can move the default and the ceiling with theBASH_DEFAULT_TIMEOUT_MSandBASH_MAX_TIMEOUT_MSenvironment variables insettings.json. - Output length: 30,000 characters by default, with a hard ceiling of 150,000 via
BASH_MAX_OUTPUT_LENGTH. Past the limit, Claude Code writes the full output to a file and hands Claude the path plus a short preview.
When the timeout fires, the command is killed and Claude sees a timeout result, not a normal exit code. That is the moment the agent gets “confused”: a server hasn’t failed, it just never returns, so the agent has nothing to act on.
For genuinely long-running processes (dev servers, watch builds), the supported answer is run_in_background: true, which starts the command as a background task and returns control immediately. List and stop background tasks with the /tasks slash command (older builds named it /bashes).
Common causes
1. Long-running command run in the foreground
npm run dev, next dev, vite, python -m http.server, tail -f — these are designed to run forever and only stop on Ctrl-C. Run in the foreground inside a Bash call, they hit the 2-minute timeout and leave the agent stuck.
How to spot it: the hung command is a server or watcher. Output shows Server running on..., ready in 800 ms, or Local: http://localhost:3000, then silence.
2. The 2-minute default was too short for the task
A real long-running task (full test suite, large build, npm install of 800 packages) legitimately takes 5+ minutes. The tool times out, the agent thinks it failed, retries, and burns more time.
How to spot it: the task is genuinely long but bounded. The tool result is a timeout message, not a command error or non-zero exit code.
3. Network call to an unresponsive endpoint
curl, wget, git fetch, git clone to an endpoint that accepts the TCP connection but never sends data. The socket stays open and the tool waits out the full timeout.
How to spot it: the command is network-bound. No progress output, no error. Often resolves if you kill and retry, or works in another terminal.
4. Command is waiting for stdin
Some commands read from stdin when not piped: git commit without -m, gh auth login, a REPL launch (python, node), ssh to a new host asking to confirm the fingerprint, anything calling read in shell. The tool has no interactive terminal to answer the prompt.
How to spot it: the command is one that would prompt a human in a real terminal. No output, indefinite wait.
5. A child process or daemon keeps the parent alive
docker compose up without -d, a postinstall script that spawns a watcher, or any script that forks into the background while the parent stays attached to a pipe.
How to spot it: initial output appears, then it hangs even though the work “looks done”. A child process is holding the parent’s stdout open.
6. A hook is wrapping the command and blocking
A PreToolUse or PostToolUse hook that hangs (a network call with no timeout, its own prompt for input) blocks the whole tool call. The command itself may have finished instantly.
How to spot it: the tool call starts but no command output appears at all. Suspect this if you have any custom hooks in ~/.claude/settings.json or .claude/settings.json.
Which bucket am I in?
Classify before you fix. The recovery differs for each category.
| Category | Examples | Symptom | Fix |
|---|---|---|---|
| Bounded (will end) | tests, builds, npm install, migrations | Timeout result before it finishes | Increase timeout, or background + poll |
| Unbounded server | dev server, watcher, daemon, tail -f | Output then silence, never returns | run_in_background: true or Ctrl-b |
| Stuck on network | curl, git fetch to dead endpoint | No output, no error, just waits | Kill, add --max-time, retry |
| Waiting on stdin | git commit, REPL, gh auth login | Would prompt a human; silent | Use non-interactive flags |
| Blocked by a hook | any command, with custom hooks installed | Tool starts, zero command output | Disable hook, narrow its matcher |
Information to collect
- The exact
Bashcommand that hung, including any wrapper. - Whether it printed any output before going silent.
- Your Claude Code version (
claude --version) — timeout behavior has shifted across releases. - Whether the same command runs and completes in a regular terminal.
- Any custom hooks in
~/.claude/settings.jsonor project.claude/settings.json. - The process tree at the time of the hang, from another terminal:
ps -ef | grep <command>.
Step-by-step fix
Step 1: Decide whether the command should ever finish
Use the table above. Get this right before choosing a fix — backgrounding a finite test run hides its exit code, and raising the timeout on a dev server just delays the same hang.
Step 2: For bounded long tasks, pass an explicit timeout
Tell Claude to set the timeout parameter on the Bash call, in milliseconds:
Run the test suite with a 10-minute timeout.
Claude issues a call equivalent to Bash(command="npm test", timeout=600000). Set it to roughly 2–3x the expected runtime so a legitimately slow run is not killed. The per-call ceiling is 10 minutes; to raise the default for every command in a project, set it in settings.json:
{
"env": {
"BASH_DEFAULT_TIMEOUT_MS": "300000",
"BASH_MAX_TIMEOUT_MS": "900000"
}
}
Known caveat (as of June 2026): some builds have ignored BASH_DEFAULT_TIMEOUT_MS / BASH_MAX_TIMEOUT_MS and enforced a lower hard ceiling (tracked in anthropics/claude-code#34138). If a task keeps dying well under your configured limit, treat it as unbounded and use the nohup workaround in Step 4 instead.
Step 3: For servers and watchers, run in the background
This is the change most people are missing. Do not redirect to a log file and append & by hand — Claude Code has a native background mode:
- While a foreground command is running, press
Ctrl-bto move it to the background. (In tmux, pressCtrl-btwice, because tmux also claims that prefix.) - Or ask Claude to start it backgrounded from the outset: “Start the dev server in the background.” Claude runs the Bash call with
run_in_background: true, gets a task ID, and immediately returns control.
Then inspect or stop it without touching another terminal:
# List running/completed background shells, with IDs, status, runtime, exit codes:
/tasks (older builds: /bashes)
# Ask Claude to read the captured output of a background task,
# or to stop it:
"Show me the last 30 lines from the dev server task."
"Stop the dev server running in the background."
Under the hood Claude reads the task’s output file and uses the TaskStop tool to kill it. The session stays usable for other work while the server runs.
Step 4: Fallback for the hard-ceiling bug — nohup + poll
If a bounded task keeps getting killed below your configured timeout (the bug noted in Step 2), detach it from the tool with nohup and poll a sentinel file across two separate Bash calls:
# Call 1 — must exit immediately:
nohup bash -c 'npm test > /tmp/test.log 2>&1; echo $? > /tmp/test.exit' &
# Call 2 — poll until the exit-code file appears, then show output:
while [ ! -f /tmp/test.exit ]; do sleep 5; done; echo "exit=$(cat /tmp/test.exit)"; tail -40 /tmp/test.log
The first call returns instantly; the second blocks only on the cheap sleep loop, which itself stays under the timeout because each poll prints. Clean up the temp files afterward.
Step 5: Add a hang-detection note to CLAUDE.md
Make Claude recognize and surface hangs instead of silently retrying. Add to CLAUDE.md:
For any Bash command, if there is no output for ~60 seconds and the command
is not known to be intentionally long-running:
1. Stop and tell me.
2. Suggest killing the process and trying an alternative.
3. Do not silently retry the same hanging command.
For long finite tasks (tests, builds), always pass an explicit timeout.
For servers and watchers, always run with run_in_background: true.
For network calls, always include a timeout flag (curl --max-time, etc.).
Step 6: Kill a shell that’s already wedged
When something is stuck right now:
# In the Claude Code session:
- Press Esc to interrupt the current tool call.
- Run /tasks (older builds: /bashes) and stop the stuck shell from the menu,
or press Ctrl-x Ctrl-k twice within 3 seconds to kill all background shells.
If the OS process is still alive afterward (e.g. a detached child), kill it from another terminal:
ps -ef | grep <command>
kill -TERM <pid> # graceful
kill -KILL <pid> # if TERM is ignored
Then tell Claude the command failed so it picks a different path instead of retrying.
Step 7: Rule out a blocking hook
If the tool call starts but produces no command output, a hook is the likely culprit. Temporarily disable hooks and retry:
mv ~/.claude/settings.json ~/.claude/settings.json.bak
# restart Claude Code, rerun the command
If it now works, the hook was blocking. Restore the file and narrow the hook’s matcher so it only runs on the commands it needs, and add a timeout to any network call inside the hook.
How to confirm it’s fixed
- The previously hanging command now either completes within its timeout, runs in the background and returns control immediately, or is recognized as stuck and surfaced to you.
/tasksshows the background server as running with a live runtime, and you can read its output on demand.- Claude Code does not silently retry the hung command.
- The session stays usable for other tasks while a server runs in the background.
- After you stop a task,
/tasksshows it as stopped andps -ef | grep <command>returns nothing.
Long-term prevention
- Default to
run_in_background: true(orCtrl-b) for anything that runs a server, watcher, or daemon. - Pass an explicit
timeoutfor any finite command expected to take more than ~30 seconds; setBASH_DEFAULT_TIMEOUT_MSinsettings.jsonif your project is full of slow tasks. - For network calls, always include a timeout flag:
curl --max-time 30,wget --timeout=30,git -c http.lowSpeedLimit=1 -c http.lowSpeedTime=30 fetch. - Avoid stdin-reading commands in agent context — pass arguments by flag (
git commit -m "msg",gh auth login --with-token,ssh -o BatchMode=yes). - Keep hooks minimal and matcher-narrow, with their own timeouts, so they cannot block unrelated work.
- Clean up background shells when you’re done so you don’t end up with a dozen leaked dev servers.
Common pitfalls
- Letting the agent retry the same hanging command three times in a row — it wastes context and never recovers.
- Setting
timeouttoo tight (e.g. 60000) for legitimate long tasks; a false hang is as disruptive as a real one. - Running
npm run devin the foreground and being surprised when the agent locks up. - Forgetting to stop background shells, so the laptop ends up with several leaked servers holding ports.
- Using
tail -fin a foreground tool call to “watch live progress” — that guarantees a hang. Background it or read the log file once.
FAQ
Q: What is the default Bash timeout in Claude Code?
A: 2 minutes (120000 ms) per command as of June 2026. Claude can request up to 10 minutes by passing the timeout parameter; change the default and ceiling with BASH_DEFAULT_TIMEOUT_MS and BASH_MAX_TIMEOUT_MS in settings.json.
Q: How do I move a command that’s already running into the background?
A: Press Ctrl-b while it’s running (twice in tmux). Claude keeps the process alive as a background task and gets control back. You can also have Claude start it with run_in_background: true from the beginning.
Q: Can Claude see a background process’s output?
A: Yes. Claude Code captures the background task’s output to a file; Claude reads it on request, or you run /tasks (older builds: /bashes) to see status, runtime, and exit codes. It does not stream output into the conversation automatically.
Q: My npm test keeps dying after about a minute even with a big timeout. Why?
A: Some builds have ignored the timeout settings and enforced a lower hard ceiling (see anthropics/claude-code#34138). Detach the task with nohup and poll a sentinel file in a separate Bash call (Step 4), or upgrade Claude Code and retry.
Q: What if a command needs interactive input (password, confirmation)?
A: Avoid interactive commands in agent context. Use non-interactive flags (--yes, --no-input, -m), pre-set credentials in env vars or config files, and pass ssh -o BatchMode=yes so a fingerprint prompt fails fast instead of hanging.
Q: How do I kill all stuck background shells at once?
A: In the session, press Ctrl-x Ctrl-k twice within 3 seconds to kill every background shell, or open /tasks and stop them individually.