解决 Claude Code 报错 The model's tool call could not be parsed
Claude Code Anthropic debugging mitmproxy如果你在 Claude Code 里频繁看到这个报错,尤其是在模型「想了很久」之后才出现:
● The model's tool call could not be parsed (retry also failed).
✳ Churned for 5m 47s
这篇文章给你一个可以直接照做的解决办法,以及背后的原因分析。
英文版见:Fixing “The model’s tool call could not be parsed” in Claude Code
先说解决方案
这个报错通常不是网络问题,而是 extended thinking 把输出 token 预算耗尽导致的。最直接的解决办法:
方法 1:关闭 always thinking(推荐)
编辑 ~/.claude/settings.json:
{
"alwaysThinkingEnabled": false
}
或在 Claude Code 会话里用 /config,把 Always thinking 关掉。
改完要新开一个会话才生效——settings 在会话启动时读取一次,中途改文件对已经在跑的会话无效。
关掉之后模型并不会变笨:它在自己判断需要时仍会思考,只是不再每一轮都强制把 token 预算花在 thinking 上。我关闭后用 effortLevel: high 甚至 xhigh 跑同样的长任务,再没有复现过这个报错。
方法 2:降低 effort 或新开 context
- 把
effortLevel从high降到更低档(如low),给 thinking 一个更小的预算上限 - 或者当前对话上下文已经很大时,新开一个干净的会话,减小输入 token、给输出留出更多空间
下面是这个结论是怎么得出来的。如果你只想解决问题,到这里就够了。
现象与最初的怀疑
我用 Claude Code(Opus 4.8 1M context,alwaysThinkingEnabled: true + effortLevel: high)跑一个长任务,反复看到这个报错。规律很明显:
- 越是长时间 thinking,越容易触发
- retry 也失败——同一个上下文重试,第二次照样报这个错
- 任务被卡死,无法继续
第一反应是怀疑网络——SSE streaming 是长连接,如果中途被 reset,tool call 的 JSON 可能收到一半就断,拼出残缺 JSON 而解析失败。但这只是猜测,需要拿数据验证。
怎么定性:抓原始 SSE 流
要区分「网络截断」和「模型侧问题」,可靠的办法是看 Claude Code 收到的原始 API 响应字节。Anthropic 的 /v1/messages 是 SSE 流式响应,关键在于那条流是怎么结束的。
我用 mitmproxy 在 Claude Code 和出口之间插一层,被动旁路记录每条响应(不改任何请求/响应头、保留流式,避免污染要诊断的现象本身):
Claude Code ──HTTPS_PROXY=8080──▶ mitmproxy(8080) ──▶ 上游出口 ──▶ api.anthropic.com
│
└─ 被动 tee 原始字节 → logs/*.sse
让 Claude Code 走 mitmproxy 的两个关键点:
- 在
~/.claude/settings.json的env里把https_proxy指向 mitmproxy(http://127.0.0.1:8080) - 把 mitmproxy 的 CA 证书塞进
NODE_EXTRA_CA_CERTS(Claude Code 是 Node 应用,靠这个信任 mitmproxy 的 TLS 中间人)
{
"env": {
"https_proxy": "http://127.0.0.1:8080",
"HTTPS_PROXY": "http://127.0.0.1:8080",
"NODE_EXTRA_CA_CERTS": "/Users/you/.mitmproxy/mitmproxy-ca-cert.pem"
}
}
注意:settings.json 的
env在启动新会话时才读取。中途改文件,已经在跑的会话不受影响。
mitmproxy 流式透传时拿到的是 content-encoding 压缩后的原始字节(这里是 gzip),所以分析时要先 zlib.decompressobj(31) 解一下。
关键证据:usage 字段
复现报错后,把对应的那条 /v1/messages 响应解压出来,事件序列长这样:
message_start model=claude-opus-4-8
content_block_start index=0 type=thinking ← 只有一个 thinking 块
content_block_delta type=thinking_delta × 31 ← 一路在思考
content_block_delta type=signature_delta ← thinking 块的签名
content_block_stop
message_delta stop_reason=tool_use ← 模型表示要调用工具
message_stop ← 然后就结束了
注意:stop_reason 是 tool_use,但整条流里没有任何 content_block_start type=tool_use 事件。模型表达了「我要调用工具」的意图,但工具调用块本体从未被发送出来。
为什么?答案在 message_delta 的 usage 里:
{
"type": "message_delta",
"delta": { "stop_reason": "tool_use" },
"usage": {
"output_tokens": 3165,
"output_tokens_details": { "thinking_tokens": 3120 },
"input_tokens": 37,
"cache_creation_input_tokens": 49060,
"cache_read_input_tokens": 0
}
}
把账算清楚:
| 项 | 数值 |
|---|---|
output_tokens(本次响应总输出) | 3165 |
其中 thinking_tokens(思考消耗) | 3120 |
| 剩余给实际内容(含工具调用) | 45 |
一个最简单的 tool-use 块,光 content_block_start 里的结构(id / name / 空 input)就要 30–40 个 token,再加上 input JSON 内容,45 个 token 不足以容纳一个完整的工具调用。
于是 API 发出了 stop_reason=tool_use(代表模型的决策),但没有剩余 token 把对应的 tool_use 内容块写出来。Claude Code 收到一个「说要调工具、却找不到工具调用」的响应,于是报 tool call could not be parsed。
这也排除了网络的嫌疑:这条流的 HTTP body 是干净结束的(message_stop 完整收到、没有连接 reset),抓到的其余几十条调用也全部正常。问题在于 token 预算的分配。
为什么「想得越久越容易出错」
因果链是这样的:
effortLevel决定 thinking 的预算上限alwaysThinkingEnabled: true决定每一轮都强制 thinking- 在一个大上下文(这里输入 + 缓存约 49K tokens)里,模型倾向于想得很深、停不下来
- thinking 把输出 token 预算的大头吃掉,留给 tool-use 的 token 不够,工具调用块发不出来
- retry 也失败,因为完全相同的上下文每次都复现同样的 token 耗尽——这是「确定性失败」的特征,也说明它不是随机的网络抖动
小结
tool call could not be parsed通常不是网络问题。在官方api.anthropic.com下,它更可能是输出 token 被 thinking 耗尽。- 「retry 也失败」是一个关键线索——确定性失败指向上下文/模型侧,随机失败才更可能是网络。
- 抓原始 SSE 是快速定位的有效手段:mitmproxy 被动记录 + 解压 + 读
usage,很快就能判断问题在哪一层。 - 大上下文 + always thinking 是容易出问题的组合:上下文越大,模型越倾向深度思考,越容易把输出预算用尽。
如果这篇文章帮你省下了排查时间,欢迎转给同样遇到这个报错的朋友。