◉CLAUDE LABEN
●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました
記事一覧/API & SDK
⬡ API & SDK/2026-05-22上級

Claude API の tool_result could not be submitted が再発しないための回復ハンドラ設計

Claude API でエージェントを長時間動かしていると、ある日突然 'tool_result could not be submitted' が連発し始めることがあります。再試行しても直らない、ストリーミング途中で死ぬ、そして次のセッションでもまた出る — 常時稼働のエージェント基盤で実際に踏み抜いた4種類の原因と、TypeScript で書いた回復ハンドラの実装を整理しました。

claude-api83tool-use23error-handling10agent-recoveryproduction87

✦ プレミアム記事

夜間に走らせているエージェントのログを翌朝に開いたら、同じ一行が延々と並んでいました。tool_result could not be submitted。一度出るとそのセッションは打ち切られ、リトライしても直らず、しばらくすると別のジョブでも同じことが起きます。個人開発の傍らで無人稼働させているエージェント基盤で、ある夜に4本のジョブが連続でこの症状にぶつかり、夜が明けるまで原因を切り分けていたことがあります。

このエラーは Anthropic の公式ドキュメントには再現条件がほとんど書かれていません。ただ、発生メッセージはひとつでも、実態は4種類のまったく別の根本原因が同じエラー文に集約されているだけ — というのが半年運用してわかったことです。リトライで直るのは1種類だけで、残りの3種類は API リクエストの組み立てを変えないと永遠に再発します。

ここでは、本番で動かしている常駐エージェントで実際に観測した4種類の発生パターンと、それぞれに対するリカバリの実装を、TypeScript のコードと一緒に整理します。

発生する4種類の根本原因

エージェントを長く動かしていると tool_result could not be submitted は次の4種類のいずれかで発生します。エラーメッセージ自体に区別はないので、API レスポンスの周辺情報と tool_use_id の対応関係から手動で切り分けます。私自身、最初の2ヶ月はこの区別がつかないまま、効かないリトライを足し続けていました。

  1. tool_use_id と tool_result の対応が壊れています: 前のターンで Claude が tool_use を返したのに、こちらが返す tool_result の tool_use_id が一致していないか、一部の tool_use に対する結果が抜けています
  2. メッセージ配列の順序が tool_use → tool_result の直後ペアになっていません: 間に text ブロックを挟んでしまったか、tool_result のメッセージを assistant ロールで送ってしまった状態です
  3. ストリーミング途中で接続が切れて parallel tool_use が中途半端になっています: 5個並列で tool_use が返ってきたうち3個まで実行した時点でクライアントが落ち、次回再開時に残り2個を実行する前に新しい user メッセージを送ってしまった状態です
  4. tool_result の content が過大です(おおむね 200KB 超): 1件あたりの返り値が大きすぎて API 側でリジェクトされています。別エラーで返ることもありますが、parallel tool_use の中の1件だけが超過しているケースでは could not be submitted に統合されて返ってきます

私の環境で観測した発生比率は、ざっくり 1番目 50% / 2番目 20% / 3番目 25% / 4番目 5% でした。1番目だけはコード上のバグ、2番目は SDK の使い方の誤解、3番目はストリーミング処理の設計ミス、4番目はツール返り値の圧縮不足です。リトライで直るのは2番目だけで、それ以外は 送信するメッセージ配列そのものを書き換えないと根治しません。

tool_use_id 不一致の検出と修復

最も多いのがこれです。Claude が parallel tool_use で複数の tool_use_id を返してきたとき、こちらが tool_result を返す側で1個でも tool_use_id を取り違えると、API は丸ごと拒否してきます。

import Anthropic from "@anthropic-ai/sdk";
import type { MessageParam, ToolUseBlock } from "@anthropic-ai/sdk/resources/messages";
 
interface ToolInvocation {
  id: string;          // tool_use_id
  name: string;
  input: unknown;
  result?: string;
  error?: string;
}
 
/**
 * 直前の assistant メッセージから tool_use ブロックを全て抽出し、
 * tool_use_id をキーにした Map を作る。実行結果はこの Map に書き戻す。
 */
function extractPendingToolUses(messages: MessageParam[]): Map<string, ToolInvocation> {
  const last = messages[messages.length - 1];
  if (last.role !== "assistant" || typeof last.content === "string") {
    return new Map();
  }
  const pending = new Map<string, ToolInvocation>();
  for (const block of last.content) {
    if (block.type === "tool_use") {
      const tu = block as ToolUseBlock;
      pending.set(tu.id, { id: tu.id, name: tu.name, input: tu.input });
    }
  }
  return pending;
}
 
/**
 * tool_result メッセージを組み立てる前に、pending と完全一致するか検証する。
 * 1件でも漏れがあれば、空の結果("tool execution skipped" 等)で穴埋めする。
 * 余計な tool_use_id を含めないことも同じくらい重要。
 */
function buildToolResultMessage(
  pending: Map<string, ToolInvocation>,
  executed: Map<string, ToolInvocation>
): MessageParam {
  const content = [];
  for (const [id, inv] of pending) {
    const done = executed.get(id);
    if (done) {
      content.push({
        type: "tool_result" as const,
        tool_use_id: id,
        content: done.result ?? "",
        is_error: Boolean(done.error),
      });
    } else {
      content.push({
        type: "tool_result" as const,
        tool_use_id: id,
        content: "tool execution skipped (client recovery)",
        is_error: true,
      });
    }
  }
  return { role: "user", content };
}

この pending と executed の差分検証を、API に送信する直前に必ず通すようにしました。私の環境では、これだけで tool_result could not be submitted の発生件数が月 80 件規模から月 5 件以下まで落ちています。

削減できた額は、次のように分解すれば手元でも数え直せます。失敗 1 件は指数バックオフを使い切るまでにおよそ 8 回再送していて、1 回あたりの入力が平均 18,000 トークンでした。差し引き月 75 件が消えた計算ですから、75 × 8 × 18,000 でおよそ 1,080 万入力トークン。Claude Sonnet 4.6 の入力単価は $3 / MTok ですので、月およそ $32 です。

金額そのものより、失敗 1 件の裏でリトライが何回走っているかを先に数えることのほうが効きます。私はこの掛け算を出すまで、失敗 1 件イコール再送 1 回だと思い込んでいて、削減効果を実際の 8 分の 1 に見積もっていました。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦tool_result could not be submitted の4種類の発生パターン(ID 不一致/順序破綻/ストリーム中断/コンテンツ過大)を、API のレスポンス例と一緒に切り分けられるようになります
✦リトライではなく状態復元で対処する TypeScript の回復ハンドラを、常時稼働させているエージェント基盤のコードベースから抜粋して持ち帰れます
✦個人開発の自動運用で実際に観測した発生頻度と、各原因に対する根治コスト・再発防止策の温度感を共有します
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

⬡ API & SDK2026-05-26
Claude API の構造化レスポンスを本番で安定させる — tool_use と JSON Schema、多層検証の実装メモ
Claude API の応答を JSON で受け取る実装は数行で書けますが、本番運用では「形は正しいが意味が壊れている」レスポンスにどう備えるかが分かれ目になります。tool_use・JSON Schema・ドメイン検証を組み合わせた多層パイプラインを、壁紙アプリの分類処理での実体験を交えて整理しました。
⬡ API & SDK2026-05-09
Claude API の 400/422/529 を“呼ぶ前”に消す5層プリフライト設計 — 月数百件の失敗呼び出しをゼロにした実装
Claude API の 400/422/529 エラーを“呼ぶ前”に止める5層プリフライト設計を、TypeScript の完全実装と運用1ヶ月分の数値でまとめました。失敗呼び出しのコスト・リトライ・通知ノイズを同時に削減できます。
⬡ API & SDK2026-04-23
Claude API の Parallel Tool Use を本番で使いこなす — 並列実行の制御・部分失敗の設計・レイテンシ最適化
Claude API の Parallel Tool Use は応答を体感で半分にしますが、部分失敗や二重書き込みの事故も増えます。asyncio.gather と Semaphore による制御、部分失敗の3方針、冪等性、観測、並列度の計測までを実運用の視点で書き残します。