Claude Code 跑了 npm run dev,然后等啊等啊等。dev server 起来了、一直跑着不退,于是 agent 以为命令”还没结束”、session 卡在一个转圈的指示器上。或者 Bash(curl https://flaky-api.com) 卡在 TCP 读上,因为对端接受了连接却从不发数据。或者脚本进了死循环,模型耐心等着永远不会来的 stdout。session 活着,但没用。
最快的修复:如果这条命令是 server、watcher、或任何本来就该一直跑的东西,就别在前台跑。要么在它运行时按 Ctrl-b 把它移到后台,要么让 Claude 用 run_in_background: true 来跑。Claude 会立刻拿回控制权、之后再去读捕获到的日志。如果是有限但很慢的任务(测试套件、大构建),那就在 Bash 调用里传一个显式的 timeout。本文剩下的部分讲怎么判断你落在哪一类,以及怎么救一个已经卡死的 shell。
Bash 工具的超时到底怎么工作(2026 年 6 月)
按官方 Claude Code 工具参考,每条 Bash 命令受两个上限约束:
- 超时:默认 2 分钟(120000 ms)。 Claude 可以通过传
timeout参数(单位毫秒)把单条命令请求到最多 10 分钟。你可以在settings.json里用BASH_DEFAULT_TIMEOUT_MS和BASH_MAX_TIMEOUT_MS这两个环境变量来调整默认值和上限。 - 输出长度:默认 30,000 字符,硬上限是 150,000(用
BASH_MAX_OUTPUT_LENGTH调)。超过限度后,Claude Code 会把完整输出写进一个文件、把路径加一段开头预览交给 Claude。
超时触发时,命令会被杀掉,Claude 看到的是一个 timeout 结果、而不是正常的 exit code。这正是 agent 开始”犯迷糊”的时刻:server 并没有失败,它只是永远不返回,所以 agent 没东西可以接着处理。
对真正的长跑进程(dev server、watch 构建),官方给的答案是 run_in_background: true:它把命令作为后台任务启动、立刻把控制权还回来。用 /tasks 这个 slash 命令来列出和停止后台任务(旧版本里它叫 /bashes)。
常见原因
1. 长跑命令在前台跑
npm run dev、next dev、vite、python -m http.server、tail -f——这些设计上就是一直跑、只能 Ctrl-C 才停。在 Bash 调用里前台跑会撞上 2 分钟超时、把 agent 卡住。
如何判断:卡住的命令是 server 或 watcher。输出里有 Server running on...、ready in 800 ms、或 Local: http://localhost:3000,然后就没声音了。
2. 默认 2 分钟对这个任务太短
真长但有界的任务(完整测试套件、大构建、800 个包的 npm install)合理就需要 5 分钟以上。工具超时、agent 以为失败了、重试、再吃更多时间。
如何判断:任务确实长但有上界。工具返回的是一条 timeout 信息,而不是命令报错或非零 exit code。
3. 网络调用打到一个不响应的端点
curl、wget、git fetch、git clone 打到一个接受 TCP 连接但从不发数据的端点。socket 一直开着、工具把整个超时耗满。
如何判断:命令是网络相关的。没有进度输出、也没有报错。kill 掉重试经常就好,或者在另一个终端里能跑通。
4. 命令在等 stdin
有些命令没被 pipe 时会从 stdin 读:不带 -m 的 git commit、gh auth login、REPL 启动(python、node)、第一次连新主机要确认指纹的 ssh、shell 里调 read 的任何东西。工具没有可交互的终端去回应这个提示。
如何判断:这条命令在真终端里会向人提示。没有输出、无限等待。
5. 子进程或 daemon 把父进程吊着
不带 -d 的 docker compose up、一个派生 watcher 的 postinstall 脚本、或任何 fork 到后台但父进程还挂在一个 pipe 上的脚本。
如何判断:开头有输出、然后卡住,虽然工作”看起来做完了”。是某个子进程把父进程的 stdout 撑着没关。
6. hook 包裹了命令、阻塞了
一个会卡的 PreToolUse 或 PostToolUse hook(没设超时的网络调用、它自己向人要输入)会阻塞整个工具调用。命令本身可能瞬间就跑完了。
如何判断:工具调用开始了、但完全没有命令输出。如果你在 ~/.claude/settings.json 或 .claude/settings.json 里装过自定义 hook,先怀疑这个。
我落在哪一类?
先分类、再修。每一类的恢复路径都不一样。
| 类别 | 例子 | 症状 | 修法 |
|---|---|---|---|
| 有界(会结束) | 测试、构建、npm install、迁移 | 还没跑完就返回 timeout | 加大 timeout,或后台跑 + 轮询 |
| 无界 server | dev server、watcher、daemon、tail -f | 有输出后沉默、永不返回 | run_in_background: true 或 Ctrl-b |
| 卡在网络 | 打到死端点的 curl、git fetch | 无输出、无报错、一直等 | kill 掉、加 --max-time、重试 |
| 等 stdin | git commit、REPL、gh auth login | 本会向人提示、却静默着 | 用非交互 flag |
| 被 hook 阻塞 | 装了自定义 hook 时的任何命令 | 工具启动、零命令输出 | 禁用 hook、收窄它的 matcher |
需要收集的信息
- 卡住的
Bash命令原文,含任何包装。 - 进入沉默前打过的输出。
- 你的 Claude Code 版本(
claude --version)——超时行为在各个版本之间变过。 - 同样的命令在普通终端里跑能不能完成。
~/.claude/settings.json或项目.claude/settings.json里有没有自定义 hook。- 卡住时的进程树,从另一个终端:
ps -ef | grep <command>。
分步修复
Step 1:判断这命令到底会不会结束
用上面那张表。一定要先分对——把一个有限的测试跑放后台会藏掉它的 exit code,而给 dev server 加大超时只是把同一个 hang 往后拖。
Step 2:有界长任务,传一个显式 timeout
让 Claude 在 Bash 调用上设 timeout 参数,单位毫秒:
用 10 分钟超时跑测试套件。
Claude 会发出一个等价于 Bash(command="npm test", timeout=600000) 的调用。把它设成预期运行时长的大约 2–3 倍,这样一次合理的慢跑不会被杀掉。单条命令上限是 10 分钟;要把一个项目里每条命令的默认值都抬高,就在 settings.json 里设:
{
"env": {
"BASH_DEFAULT_TIMEOUT_MS": "300000",
"BASH_MAX_TIMEOUT_MS": "900000"
}
}
已知坑(截至 2026 年 6 月):某些版本会忽略 BASH_DEFAULT_TIMEOUT_MS / BASH_MAX_TIMEOUT_MS、强制执行一个更低的硬上限(记录在 anthropics/claude-code#34138)。如果一个任务总是远在你配置的上限之下就被杀,就把它当无界处理、改用 Step 4 的 nohup 办法。
Step 3:server 和 watcher,放后台跑
这是大多数人漏掉的那一步。不要手动重定向到日志文件再追加 &——Claude Code 有原生后台模式:
- 当一条前台命令在跑时,按
Ctrl-b把它移到后台。(在 tmux 里要按两次Ctrl-b,因为 tmux 也占用这个前缀键。) - 或者让 Claude 一开始就以后台方式启动:“把 dev server 放后台启动。” Claude 会用
run_in_background: true跑这个 Bash 调用、拿到一个 task ID、立刻把控制权还回来。
然后不用碰另一个终端就能查看或停止它:
# 列出运行中/已完成的后台 shell,带 ID、状态、运行时长、exit code:
/tasks (旧版本:/bashes)
# 让 Claude 读某个后台任务捕获到的输出,或停掉它:
"把 dev server 任务最后 30 行给我看看。"
"停掉后台跑着的 dev server。"
底层 Claude 会读这个任务的输出文件、用 TaskStop 工具杀掉它。server 在后台跑的时候,session 还能干别的活。
Step 4:硬上限 bug 的兜底——nohup + 轮询
如果一个有界任务总在你配置的超时之下被杀(Step 2 里那个 bug),就用 nohup 把它从工具里脱出来、跨两次独立的 Bash 调用轮询一个哨兵文件:
# Call 1 —— 必须立刻退出:
nohup bash -c 'npm test > /tmp/test.log 2>&1; echo $? > /tmp/test.exit' &
# Call 2 —— 轮询到 exit-code 文件出现,再看输出:
while [ ! -f /tmp/test.exit ]; do sleep 5; done; echo "exit=$(cat /tmp/test.exit)"; tail -40 /tmp/test.log
第一个调用瞬间返回;第二个只阻塞在便宜的 sleep 循环上,而这个循环本身因为每轮都打印、所以保持在超时之内。跑完记得清掉临时文件。
Step 5:往 CLAUDE.md 加一条 hang 检测说明
让 Claude 识别并暴露 hang,而不是静默重试。加进 CLAUDE.md:
对任何 Bash 命令:如果约 60 秒没输出、且这条命令不是已知的长跑型,那就:
1. 停下来告诉我。
2. 建议 kill 掉进程、换条路试。
3. 不要静默重试同一个卡住的命令。
对有限的长任务(测试、构建),一律传显式 timeout。
对 server 和 watcher,一律用 run_in_background: true 跑。
对网络调用,一律带超时 flag(curl --max-time 等)。
Step 6:杀掉一个已经卡死的 shell
当某个东西此刻就卡着时:
# 在 Claude Code session 里:
- 按 Esc 中断当前工具调用。
- 运行 /tasks(旧版本:/bashes),从菜单里停掉卡住的 shell,
或者在 3 秒内按两次 Ctrl-x Ctrl-k 杀掉所有后台 shell。
如果之后 OS 进程还活着(比如一个 detach 出去的子进程),就从另一个终端杀:
ps -ef | grep <command>
kill -TERM <pid> # 优雅
kill -KILL <pid> # TERM 被忽略时
然后告诉 Claude 这条命令失败了,让它换条路、而不是再重试。
Step 7:排查阻塞的 hook
如果工具调用开始了、却完全没有命令输出,多半是 hook。临时禁用 hook 再重试:
mv ~/.claude/settings.json ~/.claude/settings.json.bak
# 重启 Claude Code、重跑命令
如果现在能跑了,就是 hook 在阻塞。把文件恢复回来、收窄 hook 的 matcher 让它只在需要的命令上跑,并给 hook 里任何网络调用加上超时。
怎么确认已经修好
- 原本卡住的命令现在要么在它的超时内完成、要么后台跑并立刻把控制权还回来、要么被识别为卡死并报给你。
/tasks把后台 server 显示为运行中、有一个在涨的运行时长,你随时能读它的输出。- Claude Code 不再静默重试卡住的命令。
- server 后台跑的时候 session 还能干别的活。
- 你停掉一个任务后,
/tasks把它显示为已停止,ps -ef | grep <command>也查不到东西了。
长期预防
- 任何跑 server、watcher、daemon 的东西,默认用
run_in_background: true(或Ctrl-b)。 - 任何预计超过约 30 秒的有限命令,传显式
timeout;项目里全是慢任务的话,就在settings.json里设BASH_DEFAULT_TIMEOUT_MS。 - 网络调用一律带超时 flag:
curl --max-time 30、wget --timeout=30、git -c http.lowSpeedLimit=1 -c http.lowSpeedTime=30 fetch。 - agent 上下文里别用读 stdin 的命令——参数靠 flag 传(
git commit -m "msg"、gh auth login --with-token、ssh -o BatchMode=yes)。 - hook 尽量少、matcher 尽量窄、自带超时,这样它们不会阻塞无关工作。
- 用完就清掉后台 shell,免得最后留着一堆泄漏的 dev server。
常见坑
- 让 agent 把同一个卡住的命令连着重试三次——浪费上下文、永远恢复不了。
timeout设得太紧(比如 60000)卡住合法的长任务;假 hang 和真 hang 一样难受。- 前台跑
npm run dev、然后惊讶 agent 锁住了。 - 忘了停后台 shell,电脑上最后留着几个占着端口的泄漏 server。
- 在前台工具调用里用
tail -f”看实时进度”——这等于自找 hang。放后台、或者读一次日志文件。
FAQ
Q:Claude Code 的 Bash 默认超时是多少?
A:截至 2026 年 6 月,单条命令默认 2 分钟(120000 ms)。Claude 可以通过传 timeout 参数请求到最多 10 分钟;用 settings.json 里的 BASH_DEFAULT_TIMEOUT_MS 和 BASH_MAX_TIMEOUT_MS 改默认值和上限。
Q:怎么把一条已经在跑的命令移到后台?
A:在它运行时按 Ctrl-b(tmux 里按两次)。Claude 会把进程作为后台任务保活、并拿回控制权。你也可以让 Claude 一开始就用 run_in_background: true 启动它。
Q:Claude 看得到后台进程的输出吗?
A:看得到。Claude Code 会把后台任务的输出捕获到一个文件;Claude 按需读它,或者你运行 /tasks(旧版本:/bashes)看状态、运行时长、exit code。它不会自动把输出流进对话里。
Q:我的 npm test 就算设了大超时也总在约一分钟后挂掉,为啥?
A:某些版本会忽略超时设置、强制一个更低的硬上限(见 anthropics/claude-code#34138)。用 nohup 把任务脱出来、在单独的 Bash 调用里轮询哨兵文件(Step 4),或者升级 Claude Code 再试。
Q:如果命令需要交互输入(密码、确认)怎么办?
A:agent 上下文里别用交互命令。用非交互 flag(--yes、--no-input、-m)、把凭据预设在环境变量或配置文件里,并传 ssh -o BatchMode=yes,这样指纹提示会快速失败而不是挂住。
Q:怎么一次杀掉所有卡住的后台 shell?
A:在 session 里 3 秒内按两次 Ctrl-x Ctrl-k 杀掉每个后台 shell,或者打开 /tasks 逐个停掉。
相关阅读
标签: #Claude Code #agent #排查