CLAUDE LABEN
FORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられていますFORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられています
記事一覧/API & SDK
API & SDK/2026-05-23中級

Claude API の Tool Result Submitted エラーをリトライ層で吸収する — 会話履歴を自分で修復する小さな実装

Claude API の「Tool result could not be submitted because the previous turn」エラーを、リトライ層で会話履歴を巻き戻して再送信する実装パターンと、私が壁紙アプリ運営の API 連携で実際にハマった原因の切り分け順序を書きました。

claude-api81anthropic-sdk3retry6個人開発110

5 月の頭、壁紙アプリのレビュー文要約バッチを動かしていた最中に Tool result could not be submitted because the previous turn was not a tool use というエラーが本番ジョブを止めました。1 日 8,000 件ほどのレビュー要約を回しているなかでの停止だったので、まず焦って公式ドキュメントを読み返したのですが、原因の入り口が複数あって切り分けが面倒なエラーです。同じ症状で動いている方の参考になるように、私が落ち着くまでの順序を残しておきます。

廣川政樹と申します。アーティスト・個人開発者として 2014 年から壁紙アプリ群(累計 5,000 万ダウンロード超)の運営をしており、Claude API は記事生成と AdMob レビュー対応の自動化に使っています。本記事の話は、その AdMob レビュー要約バッチを実装したときの実体験です。

このエラーが出る 3 つのパターン

最初に押さえておきたいのは、このエラーは「ツール呼び出しの直後ではない場所に tool_result ブロックが置かれた」という 1 種類の状況に集約される、ということです。ただし、発生の入り口が 3 つあるので、それを混同していると延々と直しても消えません。

  • A. 履歴の編集ミス: 過去のターンを途中で削った結果、tool_use だけが消えて tool_result だけが残った
  • B. リトライ実装のバグ: API エラーで再送するとき、tool_use 抜きで tool_result から送り直してしまった
  • C. 並列リクエストの順序崩れ: 非同期で 2 本走らせていて、tool_use 側のレスポンスが先に書き戻る前に tool_result を送信した

私が今回踏んだのは B でした。レート制限の 429 を見たときに、messages 配列の末尾だけを再送する素朴な再試行を入れていたのが原因です。Anthropic SDK のリトライ機構に任せていればよかったところを、自分でコントロールしたかったあまり巻き込み事故を起こした、というのが正直な反省点です。バッチが停止したのが朝 7 時で、AdMob のレビュー応答が滞留したまま午前中いっぱい原因を追っていたので、本番影響もそれなりに発生しました。

切り分けの順序

エラーが出たらまず、その時点で送ろうとした messages 配列をログに丸ごと出力します。Anthropic SDK の messages.create() を呼ぶ直前で json.dumps するだけで十分です。確認するのは次の 2 点だけです。

  1. 末尾 N 件を逆順に見て、tool_result ブロックの直前に必ず tool_use ブロックがあるか
  2. その tool_usetool_resulttool_use_id が一致しているか

この 2 点が満たされていればエラーは出ません。逆に言うと、満たされない原因が A・B・C のどれかを特定できれば修正は一直線です。私の場合、ログの末尾を逆順に追って 3 ターン目で tool_use_id が抜けていることに気付き、その時点で B 確定でした。最初のうちは何時間か無駄にしましたが、慣れてくると 5 分以内に切り分けられるようになります。

リトライ層で会話履歴を自動修復する小さな実装

私が壁紙アプリのレビュー要約バッチで結局採用したのは、リトライ層で会話履歴の整合性を自分で検証してから再送するパターンです。最小限の Python 実装はこの程度の量で済みます。

from anthropic import Anthropic, APIError
from anthropic.types import MessageParam
import time
 
client = Anthropic()
 
def ensure_tool_pairing(messages: list[MessageParam]) -> list[MessageParam]:
    """tool_result の直前に対応する tool_use がない要素を捨てる。"""
    safe: list[MessageParam] = []
    last_tool_use_ids: set[str] = set()
    for msg in messages:
        content = msg.get("content", [])
        if not isinstance(content, list):
            safe.append(msg)
            continue
        new_blocks = []
        for b in content:
            t = b.get("type")
            if t == "tool_use":
                last_tool_use_ids.add(b["id"])
                new_blocks.append(b)
            elif t == "tool_result":
                if b.get("tool_use_id") in last_tool_use_ids:
                    new_blocks.append(b)
            else:
                new_blocks.append(b)
        if new_blocks:
            safe.append({**msg, "content": new_blocks})
    return safe
 
def call_with_repair(messages, max_retry=3):
    for attempt in range(max_retry):
        try:
            return client.messages.create(
                model="claude-sonnet-4-6",
                max_tokens=2048,
                messages=ensure_tool_pairing(messages),
                tools=[...],
            )
        except APIError as e:
            if "tool_result could not be submitted" in str(e):
                # 履歴を再正規化してから 1 回だけ再送
                messages = ensure_tool_pairing(messages)
                time.sleep(0.5 * (2 ** attempt))
                continue
            raise
    raise RuntimeError("repair retry exhausted")

ポイントは ensure_tool_pairing を「リトライ前」と「初回送信前」の両方で必ず通すことです。私の最初の実装はリトライ前にしか通していなかったので、A のパターン(履歴編集側のバグ)で初回から落ちる場面を取りこぼしていました。

並列リクエストで踏むときに気をつけたいこと

C のケースを踏んだ知人の話で記録しておきたい点があります。彼は asyncio.gather で 2 本走らせたうち、片方が tool_use を含むレスポンスを返す前に、もう片方が tool_result を送ってしまっていました。Anthropic SDK 側は責められず、自分の状態管理がスレッド/タスク間で透けていることが原因です。

私の個人的な推奨は、tool_use を含む会話は 1 つのコルーチンに閉じ込めることです。並列化したいなら、独立した会話単位(=独立した messages 配列)として動かすほうが結局速く、デバッグも楽になります。共有状態を持ち込まないことが、結果的にスループットの上限も引き上げてくれるという経験則です。

私が今後採用しない選択肢

参考までに、検討したものの今回採用しなかった対処をいくつか挙げます。

ひとつは「tool_result ブロック自体を諦めて、ツール結果を user メッセージのプレーンテキストに埋め込む」というやり方です。これはエラーは消えますが、Claude が tools の戻り値を構造化したまま使う場面で精度が下がるのと、長期的な保守で何のためにツール定義を書いたのか分からなくなります。私自身、5,000 万ダウンロードの壁紙アプリ群を運営しながら検証ツールを少しずつ整えてきた経験から、構造を捨てる選択は最後の手段に置いておくのが賢明だと考えています。

もうひとつは「会話履歴を毎回 1 ターン目から再送する」やり方です。これはエラーの予防には効きますが、トークン消費が線形に増えるので、レビュー要約のような長期ジョブには向きません。Claude API のコストは無視できない規模になり始めると、こうした素朴な対処はすぐ採算の壁にぶつかります。

私は最終的に、上記のリトライ層を共通モジュールに切り出して、claudelab.net の記事生成パイプラインでも同じものを使っています。共通化したことで、別ジョブで同じエラーが再発したときの解析時間が体感で 1/3 に減りました。

このバッチを止めた経験から得た一番の学びは「Anthropic SDK の挙動を読み切れていないのに自前リトライを足してはいけない」ということでした。SDK の messages.create() は内部で 5xx のいくつかを自動再試行してくれていて、ユーザー側で再送する必要があるのは主に 429 と 529 だけです。私はそこを誤解して全部の例外で再送する実装を入れていたので、tool_use の途中で切れた応答に対しても再送してしまっていました。SDK のドキュメントの「Automatic Retries」のセクションを読み直したのは、エラーを潰したあとの落ち着いてからでした。

もう少し広い観点で言えば、AI 関連の API 連携で起きる障害は、純粋なネットワーク問題よりも「状態の流れ方を自分で誤解していた」という形のバグの方が圧倒的に多いと感じます。私自身、レビュー要約バッチに限らず、別の壁紙アプリで AdMob のリワード判定を組んだときも、似た形の「想定していなかった呼び出し順序」で半日溶かしたことがあります。今回はその経験があったぶん、3 つのパターンに分けて整理する発想にすぐたどり着けました。

次に試すべき 1 つのこと

エラーが出ているコードを今から触るなら、まず messages.create を呼ぶ直前で tool_use_id の対応状況を出力する print を 1 つ入れてみてください。それだけで A・B・C のどれを踏んでいるかが 5 分で分かるはずです。

5,000 万ダウンロード規模の壁紙アプリ群でも、API 連携のミスは最後まで残る種類のバグだと感じます。共に学んでいけたら嬉しいです。

シェア

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

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

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

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API & SDK2026-06-13
Managed Agents にスケジュールデプロイと vault が加わったので、自前 cron 運用との住み分けを決めた記録
6月12日の更新で Claude Managed Agents にスケジュールデプロイ・vault 環境変数・セッションスレッド Webhook が加わりました。自前の cron と平文クレデンシャル運用から何を移し、何を残すか。冪等性を軸にした判断基準と受け口の実装を残します。
API & SDK2026-05-26
Claude API の構造化レスポンスを本番で安定させる — tool_use と JSON Schema、多層検証の実装メモ
Claude API の応答を JSON で受け取る実装は数行で書けますが、本番運用では「形は正しいが意味が壊れている」レスポンスにどう備えるかが分かれ目になります。tool_use・JSON Schema・ドメイン検証を組み合わせた多層パイプラインを、壁紙アプリの分類処理での実体験を交えて整理しました。
API & SDK2026-05-02
iPhone のショートカットから Claude API を呼ぶ — 選択テキストを瞬時に整形する個人開発者向けの運用ノート
iPhone のショートカット.app から直接 Claude API を呼び、選択テキストを瞬時に整形・要約する実装手順を、個人開発者の運用視点でまとめました。Cloudflare Workers で API キーを保護する方法もあわせて解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →