Claude Code 一直卡住、转圈没反应怎么办:后端不回时它要等 6 分钟,重试满 10 次可能卡一个多小时(实测)
先回答你搜的问题
- 转圈不动、计时一直在涨(
Clauding… (4m 34s)):Claude Code 在等后端回数据,后端一个字节都没回。用 API key 加ANTHROPIC_BASE_URL(中转站、网关)时,它每次要等满 6 分钟才判定超时。- 停在
Retrying in 0s · attempt 1/10不动:上一次已经超时,这是重发的请求又在等 6 分钟,不是死机。- 会卡多久:默认重试 10 次,后端一直不回的话,实测要约 69 分钟(4147.78 秒)才最终报
Request timed out。- 马上能做的:按
Esc中断,再发一次。反复出现就是后端或网络的问题,先单独用 curl 测一下你的 base URL 通不通(命令见下文「实践效果」第 1 条)。- 想让它早点放弃:设
API_TIMEOUT_MS=60000(只能调小,调大没用)和CLAUDE_CODE_MAX_RETRIES=2。- 只在长回复时卡、短问题正常:很可能是中转站把回复缓冲到生成完才返回。生成超过 6 分钟的回复会被 Claude Code 掐断重发,永远拿不到结果。换支持流式的中转,或者让任务拆小。
- 卡在执行某条命令上(界面显示的是 Bash 而不是转圈):那是另一回事,见 Command timed out after 2m 0s。
问题背景
「Claude Code 一直卡住」是个很笼统的说法,界面上通常就是一个转圈的动词加计时,没有任何报错:
❯ 帮我把这个函数重构一下
✢ Clauding… (4m 34s)
等久了,有时会变成这样,然后又不动了:
✻ API error · Retrying in 0s · attempt 1/10
这时你无法判断它是在干活、在等网络,还是真的死了。官方文档在 Streaming idle watchdogs 和 No response from API 里列了一堆计时器,但每个计时器的生效条件都不一样,读者很难对号入座。所以这次直接把几种「卡住」做出来,量它到底等多久、界面显示什么。
问题分析
从网络上看,「卡住」只有四种可能,分别对应文档里不同的计时器:
| 卡在哪 | 后端在干什么 | 文档里管它的计时器 |
|---|---|---|
| ① 没回响应头 | 连接收了,但一直不回 HTTP 头(比如上游排队、缓冲型中转在等生成完) | 首字节超时(文档说:设了 ANTHROPIC_BASE_URL 时不生效),否则就是 API_TIMEOUT_MS(文档写默认 10 分钟) |
| ② 回了头,之后静默 | 回了 200 和 SSE 头,然后一个字节都不发 |
字节级看门狗(网关默认 300 秒)、事件级看门狗(300 秒) |
| ③ 只发 keep-alive ping | 一直发 event: ping,但没有正文 |
ping 能重置字节级看门狗,事件级看门狗不认 |
| ④ 断在半路 | 正文发了一部分,然后不动了 | 字节级看门狗 |
几个和本文结论直接相关的官方原文:
API_TIMEOUT_MS:Timeout for API requests in milliseconds (default: 600000, or 10 minutes)(环境变量文档)- 首字节超时运行在直连 Anthropic API 上,but not when
ANTHROPIC_BASE_URL… routes them through a gateway(network-config) - 自动重试:Claude Code retries transient failures up to 10 times with exponential backoff(errors)
技术方案与选型
| 方案 | 结论 | 理由 |
|---|---|---|
| 本地 stub 模拟四种卡法 | ✅ 采用 | 能精确制造「不回头 / 静默 / 只发 ping / 半路断」,每个请求带毫秒时间戳记录,配合 Claude Code 的 --debug-file 能直接看到是哪个计时器触发的 |
| 拔网线、真实中转站 | ❌ 排除 | 卡法不可控,也区分不出是哪一种;会消耗真实额度 |
| 只读文档推算 | ❌ 不能单独用 | 实测有一处和文档不一致(见下文的 6 分钟) |
隔离做法和 429 那篇 相同:env -i 清空环境、每次全新的空 CLAUDE_CONFIG_DIR、假 key、ANTHROPIC_BASE_URL 指向 127.0.0.1 上的 stub,HTTPS_PROXY 也指向 stub 以拦截一切外连。debug 日志里能看到遥测上报(1P event logging)、bootstrap、MCP registry 的外连全部被拒,没有请求到达真实服务。
-p 模式统一用 claude -p 'reply with exactly OK' --model haiku < /dev/null;交互模式在 tmux 里启动真实 TUI,每 10 秒截一帧,录了 25 分钟。
实测过程
1. 后端不回:每次等 6 分钟,不是 10 分钟
stub 收下请求,永远不回响应头。为了只看「一次请求等多久」,设 CLAUDE_CODE_MAX_RETRIES=0:
| 设置 | 结果 | 墙钟 |
|---|---|---|
API_TIMEOUT_MS 不设 |
Request timed out,exit 1 |
360.49 s |
API_TIMEOUT_MS=600000 |
同上 | 360.50 s |
API_TIMEOUT_MS=900000 |
同上 | 360.49 s |
回了头之后静默(②),API_TIMEOUT_MS=900000 |
同上 | 360.49 s |
API_TIMEOUT_MS=20000 |
同上 | 20.6 s(每次请求) |
不设置时实测是 6 分钟,不是文档写的 10 分钟。把它调大到 60 万、90 万毫秒也还是 6 分钟,调小才有效。
为了确认这个 360 秒不是实验装置造成的,又做了几组对照,结果全部还是 360 秒:
| 对照 | 目的 | 墙钟 |
|---|---|---|
CLAUDE_STREAM_FIRST_BYTE_TIMEOUT_MS=60000 |
看首字节超时有没有在起作用 | 360.50 s(无效,和文档「设了 base URL 时不生效」一致) |
CLAUDE_ENABLE_BYTE_WATCHDOG=0 |
关掉字节级看门狗 | 360.50 s |
--tools "",请求体从 183KB 降到 91KB |
看是不是按请求体大小算的 | 360.48 s |
关掉 stub(Node)自身的 requestTimeout 等全部服务器超时 |
排除「其实是 stub 先断了连接」 | 360.44 s |
最后一组很关键:Node 的 HTTP 服务器默认 requestTimeout 是 300 秒、巡检间隔 30 秒,和 360 太像了。关掉之后仍是 360 秒,而且 Claude Code 自己的日志写的是 API api_timeout after retries: Request timed out,所以这是 Claude Code 这边的行为。我在 2.1.285 的二进制里看到 SDK 客户端的超时确实写的是 API_TIMEOUT_MS 默认 600000,360 秒这个上限从哪来,没有在代码里定位到,本文只报告实测值。
2. 默认重试 10 次:可能卡一个多小时
不设任何变量、后端一直不回,这是读者「一直卡住」时最典型的情况。请求时间线(秒):
0, 361, 722, 1085, 1450, 1819, …
每 6 分钟重发一次,后几次间隔略有拉长,最长到 399 秒(疑似指数退避叠加在 6 分钟上,未在代码中确认)。完整时间线:
0, 361, 722, 1085, 1450, 1819, 2198, 2592, 2991, 3389, 3787
第 11 个请求再等满 360 秒后,在 4147.78 秒(约 69 分钟) 报 Request timed out,exit 1,stub 共收到 11 个请求。debug 日志最后一行是 API api_timeout after retries: Request timed out.。后端一直不回时,默认配置下 Claude Code 要卡 69 分钟才认输。
作为对照,API_TIMEOUT_MS=20000 时两次运行都是 11 个请求(1 次 + 重试 10 次),分别在 406.74 秒和 407.87 秒报 Request timed out。重试次数和 429 篇 测到的一样是 10 次,只是这里每次都要先等满超时。
3. 交互模式下你看到的是什么
在 tmux 里开真实会话,后端一直不回,每 10 秒截一帧,共 25 分钟。状态行的变化:
| 时间 | 界面 |
|---|---|
| 0 ~ 6 分钟 | ✢ Clauding… (4m 34s),只有计时在涨(另一个会话里动词是 Deliberating…,动词是随机的) |
| 6 ~ 12 分钟 | ✻ API error · Retrying in 0s · attempt 1/10,倒计时停在 0s 不动 |
| 12 ~ 18 分钟 | ✻ API error · Retrying in 0s · attempt 2/10 |
| 18 ~ 24 分钟 | ✻ Request timed out. · Retrying in 0s · attempt 3/10 |
| 24 分钟起 | ✻ Request timed out. · Retrying in 0s · attempt 4/10 |
「Retrying in 0s」一停就是 6 分钟,最容易让人以为死机了。其实倒计时只覆盖两次请求之间的退避间隔(这里不到 1 秒),之后新请求发出去,又是 6 分钟的等待,界面不再提示。「回了头之后静默」(②)的界面和这完全一样。
4. 只发 ping、断在半路:各有各的节奏
| 卡法 | 断开时机 | debug 日志里的原因 | 之后 |
|---|---|---|---|
| ③ 只发 ping(每 15 秒一次),默认 | 750 秒 | 第 600 秒 Streaming idle warning: no chunks received for 150s,第 750 秒 Streaming idle timeout: no chunks received for 300s, aborting stream |
重发,50 分钟时已发出第 7 个请求 |
| ③ 只发 ping(每 5 秒),字节超时调到 10 秒 | 600 秒 | 同上 | 重发,25 分钟时已发出第 3 个请求 |
| ④ 断在半路,默认 | 300 秒 | [byte-watchdog] firing: idle=300000ms → Streaming idle timeout (byte-level) |
之后每约 5 分钟重发一次,日志记为 Request timed out,50 分钟时在第 8 次重试 |
| ④ 断在半路,字节超时调到 10 秒 | 10 秒 | 同上(10000ms) | 只额外重发 1 次,再断就报 API Error: stream idle: no bytes for 10000ms 结束,共 20.3~20.7 秒 |
两个值得注意的点:
- keep-alive ping 救不了:ping 能骗过字节级看门狗,但事件级看门狗只认真正的响应事件,最终还是会判定卡死,而且比完全静默还要晚(750 秒对 360 秒)。
- 断在半路时,已经收到的那部分正文会被丢掉:
-p的 stdout 里只有最后的报错,没有半截回答。
(默认组的 4 个 case 都在 50 分钟时被实验看门狗截断,表中「之后」一列是截断时的进度,不是最终结果。)
5. 缓冲型中转站:超过 6 分钟的回复永远拿不到
有些中转站不做流式转发,而是等上游把整段回复生成完,再一次性返回。对 Claude Code 来说,这就是「① 没回响应头」,一直持续到生成结束。用 stub 模拟「等 N 秒后返回正常结果」,CLAUDE_CODE_MAX_RETRIES=1:
| 缓冲时长 | 结果 | stub 请求数 | 墙钟 |
|---|---|---|---|
| 300 秒 | ✅ 等到了,输出 OK,exit 0 |
1 | 300.33 s |
| 400 秒 | ❌ 第 361 秒被掐断重发,重发的又在第 361 秒被掐断,Request timed out |
2 | 721.29 s |
**只要单条回复的生成时间超过 6 分钟,每一次都会在第 6 分钟被掐断重发,重试多少次都没用。**短问题正常、长任务必卡,就是这个特征。
实践效果
**1. 先确认后端通不通。**用你给 Claude Code 配的地址和 key 直接打一次流式请求,看多久出第一个字节(模型名换成你的后端支持的):
curl -sN -w '\n首字节 %{time_starttransfer}s,总计 %{time_total}s\n' \
"$ANTHROPIC_BASE_URL/v1/messages" \
-H "x-api-key: $ANTHROPIC_API_KEY" -H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"claude-haiku-4-5","max_tokens":16,"stream":true,"messages":[{"role":"user","content":"hi"}]}'
这条命令会消耗一次很小的请求额度。正常情况下应该几秒内就开始逐行输出 event:。如果长时间没有任何输出,问题在后端或网络,不在 Claude Code。如果最后才一次性吐出全部内容,说明你的中转站在做缓冲(第 5 节的情况)。
**2. 卡住时按 Esc。**中断之后原消息还在,直接再发一次就行。不中断的话,最坏要等约 69 分钟(4147.78 秒)。
3. 想让它早点失败,而不是卡一个多小时:
export API_TIMEOUT_MS=60000 # 单次请求最多等 60 秒(只能调小,调大超过 6 分钟无效)
export CLAUDE_CODE_MAX_RETRIES=2 # 最多重试 2 次
注意:API_TIMEOUT_MS 设得太小,正常的长回复也会被掐断。它限制的是「整个请求」,不只是首字节。60 秒适合排查,日常使用要按你最长的任务来定。
**4. 用的是缓冲型中转站:**换一个支持流式转发的;或者把任务拆小,让单条回复在 6 分钟内生成完。调大 API_TIMEOUT_MS 解决不了,第 1 节已经证明调大无效。
**5. 不是转圈,而是卡在一条命令上:**那是 Bash 工具在等命令结束(默认 2 分钟超时),和本文的网络等待无关,见 Command timed out after 2m 0s。
本轮全部请求都打在本地 stub 上,真实 API 花费为 0。
踩坑
- **差点把实验装置的超时当成结论。**360 秒和 Node 默认的
requestTimeout(300 秒)加巡检间隔太接近了。在关掉 stub 自身全部超时、确认仍是 360 秒之前,这个数不能写。 - **心算把 6 分钟算成了 10 分钟。**看 debug 日志时把 15:04:43 → 15:10:43 当成了 10 分钟,一度以为和文档一致。时间差一律用脚本算。
- **hang 的「连接关闭」时间戳是错的。**最初用
req.on('close')记录连接关闭,Node 在请求体读完时就会触发它,所以全是 0 秒。最终以请求到达的时间戳和 Claude Code 自己的 debug 日志为准。
未验证清单:直连 Anthropic API(不设 ANTHROPIC_BASE_URL)时的超时,按文档首字节超时会生效(180 秒),本文没测;OAuth 订阅登录下的行为;HTTPS 的真实中转站(本文的 stub 是本机 http);360 秒上限的代码来源;只发 ping 的情况在 1500 秒后重发间隔从 750 秒变成约 300 秒的原因;断在半路时,字节超时 10 秒和默认 300 秒的重试次数为什么不同(日志里两者的错误类型不同,一个是 stream_idle_timeout,一个是 Request timed out,但没有在代码里确认)。