tool_resultのis_errorでエラーを正しく伝える書き方
is_errorに書くメッセージの質でClaudeの立て直し方が変わります。良い例・悪い例、必須パラメーター欠落時のリトライ挙動、サーバーツールとの違いをまとめます。
is_errorの書き方でClaudeの立て直し方が変わる
ツールの実行が失敗したとき、tool_resultブロックのcontentにエラー内容を書き、is_error: trueを添えて返します。Claudeはこの内容を読んで、ユーザーへの説明を組み立てたり、別のやり方を試したりします。contentに何を書くかで、Claudeがそのエラーから立て直せるかどうかが変わります。
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "ConnectionError: the weather service API is not available (HTTP 500)",
"is_error": true
}is_errorが対象にするのは、クライアントツール(自分のコードで実行するツール)です。Web検索のようなサーバーツール(API側が実行するツール)のエラーはAPI側が内部で処理するため、is_errorを自分で扱う必要はありません(後述)。対象になる失敗は大きく2種類あります。ツールは正しく呼ばれたが実行結果がエラーだった場合と、そもそも呼び出し自体が無効だった場合です。Browser use toolのようなAnthropic定義のクライアントツールセットには、この基本形に上乗せされる規約があります。toolset_nameのechoやbrowser_stateの扱いはBrowser use toolのエラーハンドリングにまとめています。
「failed」で終わらせない — 指示的なメッセージを書く
"failed"のような一般的なエラー文言だけを返すと、Claudeは何が起きたのか、次に何を試せばよいのかを推測するしかありません。何が問題で、Claudeが次に何を試すべきかを含めた指示的なメッセージにすると、推測させずに回復や調整の材料を渡せます。
| 悪い例 | 良い例 |
|---|---|
"failed" | 良い例"Rate limit exceeded. Retry after 60 seconds." |
"error" | 良い例"Missing required 'location' parameter" |
"invalid" | 良い例"'unit' must be 'celsius' or 'fahrenheit', got 'C'" |
"unauthorized" | 良い例"API key expired. Re-authenticate before retrying." |
"not found" | 良い例"No customer with id 'cus_98x2'. Check the id and retry." |
"timeout" | 良い例"Upstream service timed out after 30s. Safe to retry once." |
contentは必ずしも1文の文字列である必要はありません。他のtool_resultと同じく、textブロックを複数並べたり、image・document・search_resultブロックを添えたりできます。エラーの原因が複雑で、状況の説明とログの抜粋を分けて渡したいような場面では、この構造化された形が役立ちます。
良い例はどれも2つの要素を含みます。何が起きたか(レート制限、パラメーター欠落、値の不正)と、次にどうすればよいか(60秒待つ、locationを補う、値を修正する)です。この2要素が入っていれば、Claudeは推測せずに回復や調整へ進めます。
必須パラメーター欠落はClaudeが自分で訂正する
Claudeのツール呼び出しが無効(必須パラメーターの欠落など)だった場合、通常はClaudeがそのツールを正しく使うための情報が足りていなかったサインです。開発中にこのエラーを見かけたら、まず疑うのはツール側のdescriptionです。パラメーターの意味・形式・単位をより詳しく書き直してから再試行すると、同じ入力でも通るようになることがあります。公式の説明は開発中の話です。本番でも毎回発生するなら、同じ考え方でdescriptionを見直す手があります。
会話を先に進めたい場合は、エラーを示すtool_resultを返して継続することもできます。
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: Missing required 'location' parameter",
"is_error": true
}呼び出しが無効だった場合の流れは次のとおりです。
無効な呼び出しからの立て直し
- 1
無効な呼び出しが届く
必須パラメーターが欠けた
tool_useが返ります。 - 2
is_error付きで返す
欠けているものを書いた
tool_resultを、is_error: trueで返します。 - 3
Claudeが2〜3回訂正する
欠けた情報を補って、同じツールを呼び直します。
- 4
直らなければ謝罪する
訂正が続かなかったときに、ユーザーへ謝罪します。
contentに欠けているものを具体的に書いておくほど、この訂正は通りやすくなります。
無効な呼び出しが起きる原因は1つではありません。is_errorで1件ずつ訂正させる前に、症状から原因を特定しておくと同じ失敗の再発を防げます。
| 症状 | よくある原因 | 対処 |
|---|---|---|
| 存在しないパラメーターを送ってくる | よくある原因strict未使用時のモデルの過剰生成 | 対処strict: trueを追加 |
| enumの範囲外の値を送ってくる | よくある原因strict未使用、またはenumが大きすぎる | 対処enumを絞る、またはinput_examplesで妥当な値を示す |
| 意図したツールを呼ばない | よくある原因ツール名の衝突、スキーマが汎用的すぎる | 対処ツール名の重複を確認し、input_examplesで用途を明確にする |
これらはいずれもdescriptionやinput_schema側の設計に手を入れる話で、is_errorのcontentをどう書くかという本記事の主題とは層が異なります。実行時の訂正(is_error)と設計時の予防(description・strict・input_examples)を、両輪として使い分けるのが実務的です。
実際のリクエストで確認する
get_weatherが接続エラーを起こした状況を、is_error付きのtool_resultで伝えるリクエストは次のようになります。
curl -sS https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-5-5",
"max_tokens": 1024,
"tools": [{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"]
}
}],
"messages": [
{"role": "user", "content": "What is the weather in Tokyo?"},
{"role": "assistant", "content": [
{"type": "tool_use", "id": "toolu_01", "name": "get_weather",
"input": {"location": "Tokyo, Japan"}}
]},
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "toolu_01",
"content": "ConnectionError: weather service unreachable. Safe to retry once after a short delay.",
"is_error": true}
]}
]
}'is_error: trueがあっても、メッセージ配列としての整形ルール(tool_resultを先頭に置く、直前のassistantメッセージの直後に置く)は変わりません。エラーかどうかはis_errorフィールドが担い、位置関係は通常のtool_resultとまったく同じです。整形自体を間違えると、is_errorの中身がどれだけ丁寧でも400エラーで弾かれ、Claudeに読んでもらう前に会話が止まります。
サーバーツールのエラーはis_errorの対象外
Web検索のようなサーバーツールでネットワーク障害などが起きた場合、Claudeが内部で透過的に処理し、代替の応答や説明をユーザーに提示しようとします。クライアントツールとは異なり、サーバーツールの結果には自分でis_errorを設定する必要がありません。
Web検索固有のエラーコードには次のようなものがあります。
| コード | 内容 |
|---|---|
too_many_requests | 内容レート制限超過 |
invalid_input | 内容検索クエリのパラメーターが不正 |
max_uses_exceeded | 内容Web検索ツールの最大使用回数を超過 |
query_too_long | 内容クエリが上限の長さを超過 |
unavailable | 内容内部エラーが発生 |
クライアントのエラーハンドリングと同じ設計で扱おうとすると、存在しないコードパスを実装することになります。
違いは、実行の主体がどちらにあるかです。
is_errorを返す側はどちらか
クライアントツール
結果をAPIへ送り返す必要があります。成否を伝えるのは開発者側なので、失敗時はis_error: trueを付けて返します。
サーバーツール
実行まで内部で完結し、成否はレスポンスに含まれて返ります。開発者がtool_resultを作る場面がないので、is_errorの出番もありません。
複数呼び出しのうち一部だけ失敗したとき
1つのassistantターンで複数のtool_useが返ることがあります。順番に実行していて途中の呼び出しが失敗した、あるいは意図的に一部の実行をスキップした場合でも、そのtool_use_idに対応するtool_resultは省略できません。 is_error: trueと簡潔な説明を付けて、必ず返します。
{
"type": "tool_result",
"tool_use_id": "toolu_02",
"content": "Skipped: an earlier tool call in this batch failed",
"is_error": true
}対応するtool_resultが1つでも欠けると、次のリクエストは整形ルール違反として400エラーになります。「失敗したから何も返さない」という省略は選べません。
並列で走らせた呼び出しが、前提の未完了で失敗する場合もあります。そのときは、そのままのエラーメッセージをis_error: trueで返せば、Claudeが次のターンで呼び直します。依存する呼び出しが並ぶのを減らすには、システムプロンプトに次の一文を足す方法があります。「Only batch tool calls that are independent of each other.」
順に実行して最初の失敗で止める方法も有効です。computer useとbrowser useのバッチ操作では、この止め方が必須です。走らせなかった呼び出しにはis_error: trueを返します。
エラー内容の記述と、指示の埋め込みは別物
指示的なメッセージを書くという原則には、注意すべき境界があります。ツール結果には、ウェブページや外部APIなど、制御できない出どころの内容が入りがちです。公式は、そうした内容を開発者が信頼できないものとして扱い、systemプロンプトやuserのテキストではなくtool_resultに入れるよう勧めています。誰かが仕込んだ指示でClaudeの動きを変えようとする間接プロンプトインジェクションを警戒するためです。
そのため、contentの中に「常に日本語で答えて」「今後は箇条書きでまとめて」のような、そのツールの実行結果と関係のない振る舞いの指示を混ぜるのは避けたほうが安全です。
この挙動と、is_errorで推奨される「次に何を試すべきか」を書くこととは矛盾しません。"Retry after 60 seconds."のような一言は、そのツール呼び出し自体の結果としての回復手がかりであり、会話全体の振る舞いを変える指示ではないからです。ツールの実行結果と無関係な振る舞いの指示は、tool_resultの外(tool_resultのあとに続けるuserターンなど)に置くのが安全な切り分け方です。
原因別のメッセージの書き分け例
外部サービスの障害やアカウントの権限不足は、多くの場合、パラメーターを書き直しても結果が変わりません。「次に何を試すか」を原因で分けて書く例を、次の3通りで挙げます。
原因別の書き分け例
入力の誤り
欠けているものや正しい値を書きます。例:
'unit' must be 'celsius' or 'fahrenheit', got 'C'待てば直る
待つ秒数を書きます。例:
Rate limit exceeded. Retry after 60 seconds.再試行しても直らない
再試行の余地がないと書きます。例:
This account does not have permission for this operation. Do not retry.
自前で組むか、SDKに任せるか
ここまでの内容は、ツール呼び出しループを自分のコードで回している場合に直接関わってきます。Tool RunnerでAnthropic APIのツール呼び出しループを自動化する設計では、ツールが投げた例外をSDKが捕まえ、is_error: trueのtool_resultとしてClaudeに返します。
返る内容は例外のメッセージです(Pythonでは型とメッセージ)。スタックトレースは含まれません。つまり、例外のメッセージがそのままcontentになるので、メッセージの質は呼び出し側で作り込む部分として残ります。
SDKのログと差し込み口は言語で違います。PythonのSDKは、ツールが未処理の例外を出すとスタックトレースごと標準のloggingに記録します。Python以外のSDKは、失敗したツールを記録しません。原因を見たいときは、ツール関数の中で例外を捕まえて自分でログに残します。
返す前にエラーを検査して止めたいときは、Pythonならrunner.generate_tool_call_response()、TypeScriptならrunner.generateToolResponse()で、返す前のtool_resultを受け取り、is_errorを調べられます。C#はBetaToolErrorを投げて、返す内容を自分で指定します。GoとJavaとPHPのSDKには、返す前に割り込む仕組みが今のところありません。GoとJavaは、ツール関数の中で例外を捕まえて自前の内容を返します。PHPは、pushMessages()で自分で組んだtool_resultを渡します。ツール呼び出し以外のHTTPレベルのエラー(レート制限・認証・タイムアウトなど)まで含めた設計はClaude APIのエラーハンドリング設計で扱っています。両者は別のレイヤーのエラーで、is_errorが対象にするのはツールの実行結果、あちらが対象にするのはリクエスト自体の成否です。
まとめ
contentには「何が起きたか」と「次にどうすればよいか」の2つを入れます。同じ無効な呼び出しが何度も起きるなら、is_errorで毎回訂正させるより、ツール定義のdescriptionを直すほうが根本的な対処になります。