==============
== ACE BLOG ==
==============

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

この記事では、すぐに適用できる対処法と、実際の原因の分析をまとめます。

先に重要な結論を述べておきます。これは本質的にモデル側の問題であり、あなたのローカル設定やネットワークの問題ではありません——根本原因は、大きなコンテキストでモデルが深く思考するうちに出力トークンの予算を使い切ってしまい、tool-use ブロックが送出されなくなることです(詳細は後述)。そして 一部の地域のユーザーでのみ再現します。同じバージョン・同じ設定でも、地域や経路によって発生率が大きく異なり、特に JP(日本)リージョンでは深刻なようです。周りの誰かが遭遇していなくても、あなたの環境が壊れているわけではありません。したがって本記事の対処法は 「緩和策」であって「根治」ではありません——発生率は大きく下げられますが、根本原因がモデル側にあるため完全になくなる保証はありません。完全に回避したいなら、一時的に旧バージョンのモデルに切り替えるのが確実です(例:Opus 4.8 から前世代の Opus / Sonnet へ)。旧モデルはこの問題の影響を受けないので、今のところ最も信頼できる回避策です。公式の修正が出たら新しいモデルに戻せば構いません。

中文版:解决 Claude Code 报错 The model’s tool call could not be parsed English: Fixing “The model’s tool call could not be parsed” in Claude Code


まず対処法から

このエラーは通常 ネットワークの問題ではありません。モデル側の挙動——extended thinking が出力トークンの予算を使い切ることが原因です。以下はいずれも発生率を大きく下げる 緩和策 です。完全に回避したい場合は方法 3(旧モデルへの切り替え)へ。

方法 1:always thinking を切る(推奨)

~/.claude/settings.json を編集します:

{
  "alwaysThinkingEnabled": false
}

または Claude Code のセッション内で /config を使い、Always thinking をオフにします。

反映には新しいセッションを開始する必要があります——settings.json はセッション起動時に一度だけ読み込まれるため、実行中のセッションで途中編集しても効きません。

オフにしてもモデルが劣化するわけではありません。必要と判断したときには引き続き思考しますが、毎ターン 強制的にトークン予算を思考に費やすことがなくなるだけです。オフにしたあと、同じ長時間タスクを effortLevel: high、さらには xhigh で走らせましたが、このエラーは二度と再現しませんでした。

方法 2:effort を下げる、またはコンテキストを新規に

  • effortLevelhigh から低いティア(例:low)に下げて、思考の予算上限を抑える
  • あるいは現在の会話コンテキストが既に大きい場合は、クリーンなセッションを開始 して入力トークンを減らし、出力に余地を残す

方法 3:一時的に旧バージョンのモデルへ切り替える(最も確実な回避策)

最初の二つは発生率を下げるだけで、根本原因がモデル側にあるため完全になくなる保証はありません。締め切りに追われていてこのエラーに邪魔されたくないなら、最も確実なのは 一時的に前世代のモデルへ戻すこと です——例:Opus 4.8 から前の Opus または Sonnet へ。旧モデルはこの問題の影響を受けません。公式の修正が出たら新しいモデルに戻しましょう。

対処だけが目的ならここで終わって構いません。以下はこれらの結論に至った過程です。


症状と最初の疑い

Claude Code(Opus 4.8 1M context、alwaysThinkingEnabled: true + effortLevel: high)で長時間タスクを走らせていて、このエラーに繰り返しぶつかりました。パターンは明確でした:

  • 思考が長いほど発生しやすい
  • 自動 リトライも失敗 ——同じコンテキストで再試行しても同じエラーになる
  • タスクが完全にブロックされて先に進めない

最初に疑ったのはネットワークでした。SSE streaming は長時間の接続を使うため、途中でリセットされると 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 経由にする要点は二つ:

  1. ~/.claude/settings.jsonenv ブロックで https_proxy を mitmproxy(http://127.0.0.1:8080)に向ける
  2. 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"
  }
}

注意: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_reasontool_use なのに、ストリームのどこにも content_block_start type=tool_use イベントがありません。モデルは「ツールを呼びたい」という意図を示したのに、tool-use ブロックの本体は一度も送出されていません。

なぜか。答えは message_deltausage フィールドにあります:

{
  "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 トークン、さらに input の JSON が必要です。45 トークンでは完全なツール呼び出しを収めきれません。

そのため API は stop_reason=tool_use(モデルの 判断)を送出したものの、対応する tool_use コンテンツブロックを書き出すトークンが残っていませんでした。Claude Code は「ツールを呼ぶと言っているのにツール呼び出しが含まれていない」レスポンスを受け取り、tool call could not be parsed と報告したわけです。

これでネットワークの疑いも晴れます。このストリームの HTTP ボディはきれいに終わっており(message_stop を完全に受信、接続リセットなし)、捕まえた他の数十件の呼び出しもすべて正常でした。問題はトークン予算の配分にあります。


なぜ「思考が長いほど悪化する」のか

因果の連鎖はこうです:

  • effortLevel が思考予算の 上限 を決める
  • alwaysThinkingEnabled: true毎ターン 思考を強制する
  • 大きなコンテキスト(ここでは入力 + キャッシュで約 49K トークン)では、モデルは深く考え込んで止まりにくくなる
  • 思考が出力トークン予算の大半を食い、tool-use ブロックに残る分が足りず、送出できない
  • リトライも失敗 する。まったく同じコンテキストが毎回同じ枯渇を再現するからで、これは 決定論的な 失敗の特徴であり、ランダムなネットワークの不安定さではない証拠でもある

まとめ

  1. tool call could not be parsed は通常ネットワークの問題ではなく、モデル側の挙動です。公式の api.anthropic.com では、出力トークンが思考に使い尽くされることが原因です。
  2. これは モデル側の問題 で、一部の地域でのみ再現 します(JP リージョンが特に深刻なようです)——あなたのローカル設定やネットワークが壊れているわけではありません。
  3. 「リトライも失敗」は重要な手がかり——決定論的な失敗はコンテキスト/モデル側を指し、ランダムな失敗のほうがネットワークの可能性が高い。
  4. 生の SSE を捕まえるのが切り分けの有効な手段:mitmproxy のパッシブ記録 + 展開 + usage を読む、で問題がどの層にあるかすぐ分かります。
  5. 大きなコンテキスト + always thinking は問題が起きやすい組み合わせです。コンテキストが大きいほどモデルは深く考え込み、出力予算を使い切りやすくなります。always thinking を切る・effort を下げるは緩和策。一時的に旧モデルへ戻す のが最も確実な回避で、その後は公式修正を待ちましょう。

この記事でデバッグの時間を節約できたなら、同じエラーに遭遇している人に共有してもらえると嬉しいです。