Browser use toolのエラーハンドリング — executor側のis_error規約
Browser use toolでexecutorが失敗をどう返すか、navigateのスキーム拒否やバッチアクション停止の実例から読み解きます。
Browser use toolのエラーはexecutorが形にする
Browser use toolは、Claudeがブラウザを操作するためのbrowser_toolset_20260801というクライアントツールセットです。navigateやleft_clickなど27個のメンバーツールを既定で宣言でき、javascript_execのような残り4つはconfigsで有効化した場合だけ追加されます(最大31個)。実行そのものは常にアプリケーション側のexecutorが担います。Anthropicのサーバーはtool_useブロックを返すだけで、ブラウザは動かしません。
この構造では、呼び出しが失敗したときにClaudeへ何を返すかがexecutor側の設計事項になります。失敗した呼び出しは、通常のエラー結果として返すのが公式ドキュメントの基本形です。
エラー結果の基本形
付けるもの
is_error: true、何が起きたかを書いたtext content、呼び出しと同じtoolset_nameのechoの3点です。付けないもの
browser_stateブロック。is_error: trueの結果に載せると、リクエストがinvalid_request_errorで弾かれます。返す相手
実行しなかった呼び出しも含め、
tool_use1つにつきtool_resultを1つ返します。
形そのものは単純で、難しいのは文言の書き方と、navigateのスキーム拒否のような個別ケースの扱いです。
失敗が返る場所は2つある
executorが組み立てるis_errorの結果と、APIが会話の前に突き返す400は、別の層の失敗です。どちらで起きた問題かを先に切り分けると、直す場所が決まります。
エラーの出どころで直す場所が変わる
executorが返すis_error
ページが開かない、参照が見つからない、メンバーが未実装といった実行時の失敗です。Claudeは文言を読んで次の手を決めます。直すのは文言の具体性です。
APIが返すinvalid_request_error
tool_resultの形が規約に反したときの拒否で、Claudeが動く前に起きます。直すのは文言ではなく、フィールドとブロックの組み立てです。
後者の具体的な原因は、後半の「リクエスト自体が拒否される400エラー」の表にまとめています。
navigateハンドラーで拒否するスキーム
セキュリティ上の推奨事項の中でも、navigateハンドラーの実装は具体的な規約を伴います。公式ドキュメントは次の4点を挙げています。
- 履歴操作のキーワード
"back"・"forward"・"reload"を受け付ける - スキームの無いURLは
https://として扱う - URLをパースしたうえで、
http・https以外のスキーム(javascript:・file:・data:・chrome:など)をエラー結果で拒否する - スキームの判定は文字列の前方一致ではなく、URLパーサーで行う
3番目と4番目は対になっています。文字列マッチだけで判定すると、細工されたURLがスキーム拒否をすり抜ける余地が残ります。APIはClaudeが開くURLをフィルタしないため、拒否できるのはexecutor側だけです。実際に拒否したときのエラー結果は次の形になります。
{
"type": "tool_result",
"tool_use_id": "toolu_01LeUTyqkhRxBFq1QTG3pkwN",
"toolset_name": "browser",
"is_error": true,
"content": "Error: Navigation refused. Only http and https URLs are allowed."
}文言はClaudeが次に何をするかを左右する
エラーのtext contentは、単なる通知ではなくClaudeが読んで行動を調整する入力です。公式ドキュメントはError: navigation failedのような素っ気ない文言と、Error: Navigation to https://example.com/status timed out after 30 seconds. The page may be unavailable.のような具体的な文言を対比しています。後者だけがClaudeにとって行動の手がかりになるという立場です。
失敗したURL、待った秒数、ページ側に原因がありそうかまで書き込むと、Claudeは再試行するか別の手段に切り替えるかを判断できます。何が起きたかに加えて、次にどう解釈するかまで書くのが実務上のポイントです。一般的なツールでの文言設計はtool_resultのis_errorでエラーを正しく伝える書き方にまとめています。
4つの典型パターンと返す文言
スキーム拒否以外にも、公式ドキュメントは3つの典型的な失敗パターンを例示しています。いずれもis_error: trueとtoolset_name: "browser"は共通で、contentの文言だけが変わります。
| ケース | 返すcontentの例 |
|---|---|
| スキーム拒否 | 返すcontentの例Error: Navigation refused. Only http and https URLs are allowed. |
| 古い・存在しない要素参照 | 返すcontentの例Error: ref_3 is stale or not found on the current page. Re-read the page to get fresh references. |
| 無効化・未実装のメンバー | 返すcontentの例Error: javascript_exec is not enabled in this environment. |
| バッチ内の後続呼び出し | 返すcontentの例Not executed: an earlier action in this turn failed.(文言固定) |
「無効化されたメンバー」は、configsで無効にしたメンバーをClaudeが呼んでしまったケースです。client toolsetsの仕様では、無効化したメンバーはClaudeに見えるツール一覧から除かれます。それでもClaudeが名指ししてきた場合は、呼び出しを捨てずにエラーのtool_resultで答えます。
「古い要素参照」のref_3は、read_pageやfindがexecutorの側で割り当てた参照です。見つからない、または古い参照を受け取ったら、文言の後半にあるとおり、ページを読み直して新しい参照を取るようClaudeに伝えます。
バッチアクションは最初の失敗で打ち切る
1つのターンで複数のメンバー呼び出しが並ぶ「バッチアクション」では、失敗の伝播ルールが単発呼び出しと異なります。
バッチ内で失敗が起きたときの処理
- 1
記載順に実行する
並行ではなく、
tool_useが並んだ順に1つずつ実行します。 - 2
最初の失敗で止める
失敗した呼び出しには、executor自身の具体的なエラー文言を返します。
- 3
残りを固定文言で埋める
それ以降の呼び出しはすべて
is_error: trueとNot executed: an earlier action in this turn failed.で答えます。 - 4
省略せずに全件返す
実行しなかった呼び出しの
tool_resultも、同じuserメッセージに含めます。
検索ボックスをクリックし、文字を入力し、Enterキーを押す3つの呼び出しが1ターンにまとまっている場面を考えます。最初のクリックが古い参照エラーで失敗すると、続くtypeとkeyは実行されません。2つにはhalt文言のエラー結果が返ります。
Claudeは3つの呼び出しを「クリックが成功した前提」で組み立てています。実行しなかった呼び出しにも結果を返すのは、会話の整合性を保つためです。並列呼び出しの一部が失敗する一般的なケースは並列ツール呼び出しが一部失敗したときのis_errorの返し方でも扱っています。
この規約に沿ったexecutorの骨組みを、Pythonで書くと次のようになります。
HALT = "Not executed: an earlier action in this turn failed."
def run_batch(blocks, handlers):
results, failed = [], False
for b in blocks: # tool_useを記載順に処理する
base = {"type": "tool_result", "tool_use_id": b.id,
"toolset_name": "browser"}
if failed:
results.append({**base, "is_error": True, "content": HALT})
continue
try:
if b.name not in handlers:
raise ExecutorError(
f"{b.name} is not enabled in this environment.")
results.append({**base, "content": handlers[b.name](b.input)})
except ExecutorError as e:
failed = True
results.append({**base, "is_error": True,
"content": f"Error: {e}"})
return resultsエラー側のブロックにbrowser_stateを載せない点は、is_errorの分岐が自然に守ってくれます。成功側のハンドラーがbrowser_stateを組み立てる処理は、この骨組みの外に置いています。ExecutorErrorは手元の例外クラスで、ブラウザ自動化ライブラリの失敗をこの型に包む想定です。
tab_idが不正なときもエラー結果で返す
switch_tabとclose_tabのtab_idはClaudeが入力として渡します。APIは成功結果のテキストにこの値を埋め込むため、tab_idにはbrowser_stateと同じ制限がかかります。4,096文字以内であること、制御文字や改行を含まないこと、Unicodeの行区切り・段落区切りを含まないことです。
この制限に反するtab_idが来たら、browser_stateを組み立てて返してはいけません。エラー結果で答えます。switch_tabのinput.tab_idはAPIが結果のテキストに差し込むため、制限を超えた値はbrowser_stateの形では返せません。
ダウンロード失敗はis_errorでは報告しない
すべての失敗がis_errorとcontentの組み合わせで表現されるわけではありません。クリックやナビゲーションがファイルダウンロードを開始し、そのダウンロードが失敗・キャンセルされた場合はdownload_failedという別の経路で報告します。これは成功した呼び出しのbrowser_stateブロックが持つstate_changes配列に載せるイベントで、download_id・url・任意のerrorフィールドを持ちます。
ここで公式ドキュメントが釘を刺しているのが、is_error: trueの結果にstate_changesを送ってはいけないという規約です。ダウンロードの失敗がエラー呼び出しの最中に起きた場合も、そのstate_changesは当の結果には乗せません。次に成功した結果まで持ち越して報告します。browser_stateをエラー結果に付けない規約と、この持ち越しルールは同じ原則の裏表です。
download_id・url・path・errorには、4,096文字以内で制御文字を含めないという上限があります。ブラウザが返す生のエラー文字列は、転記する前にこの上限内へ整形します。urlはリダイレクトを経たあとの最終URLで、署名付きのクエリ文字列に認証情報が含まれることがあります。Claudeの文脈に入れたくないクエリパラメータは、報告前に落とします。
リクエスト自体が拒否される400エラー
executorが返すis_errorとは別に、リクエストの形が規約に反すると、Claudeが応答する前にAPIがinvalid_request_errorで弾きます。整形ルール全般はtool_resultとtool_useの400エラーで扱った内容と地続きです。Browser use toolでは、次の組み合わせがこのエラーになります。
| 何を送ったか | 規約 |
|---|---|
ツールセットのエントリにname・strict: true・input_examples・defer_loadingを付ける、2つ目のbrowserツールセットを置く、browserやメンバーを名指しするtool_choiceを使う | 規約クライアントツールセットが受け付けない指定 |
toolset_nameのechoが無い、または値が違う。メンバー呼び出しでない結果にtoolset_nameを付ける | 規約メンバーの結果にだけ、呼び出しと同じ値を付ける |
text・image・browser_state以外のブロックを含む | 規約メンバー結果が許すブロック型はこの3種類 |
前ターンのメンバー呼び出しに対応するtool_resultが無い | 規約実行しなかった呼び出しにも結果を返す |
is_error: trueの結果へのbrowser_state、空のstate_changes、同じdownload_idの重複 | 規約browser_stateの規約違反。何も報告しないときはブロックや配列を省く |
成功したnew_tab・switch_tab・close_tab・list_tabsの結果がbrowser_state1つちょうどではない | 規約タブ管理系メンバーの結果はbrowser_state1つだけ |
| 画像がモデルの画像サイズ上限を超える | 規約APIは自動で縮小しない |
browser_toolset_20260801に対応しないモデルを指定する | 規約対応モデルは後述 |
画像の行には補足があります。リクエストに20枚を超える画像が含まれるときは、より厳しいサイズ上限が適用されます。数えるのは過去の結果に含まれるscreenshotやzoomの画像も含めた枚数です。画像の扱いはモデルの画像サイズ上限に従い、返す前にexecutor側でリサイズしておきます。
toolset_nameのechoは、成功結果にもエラー結果にも共通する規約です。Browser use toolのメンバー呼び出しに返すtool_resultは、通常のカスタムツールへのtool_resultと違い、この値を必ず持ちます。通常の形で整えた結果をそのまま返すと、ここで400になります。
Computer use toolとの共通点と違い
Browser use toolとComputer use toolはどちらもAnthropic定義のクライアントツールセットです。toolset_nameのechoや、最初の失敗でバッチを止める規約など、エラー処理の骨格は共通です。Computer Useツールを自前実装する最小構成の実装パターンも、骨格はそのまま流用できます。
違いは2点あります。1つは打ち切り後の文言で、両ページは別の固定文を定めています。
| ツールセット | toolset_name | バッチ後続の固定文言 |
|---|---|---|
| Browser use | toolset_namebrowser | バッチ後続の固定文言Not executed: an earlier action in this turn failed. |
| Computer use | toolset_namecomputer | バッチ後続の固定文言Not executed: an earlier computer action in this turn failed. |
toolset_nameの値だけ差し替えて文言を使い回すと、Computer use側の固定文と食い違います。もう1つはbrowser_stateで、これはブラウザのタブ状態を追跡するBrowser use toolだけの仕組みです。Computer use toolの結果には登場しません。
両ツールセットはscreenshotやkeyのようにメンバー名が重なります。同じリクエストに両方を宣言したときは、nameだけでなくtoolset_nameとの組で呼び出しを振り分けます。
対応モデルとプラットフォームの範囲
エラーハンドリングを実装する前提として、Browser use toolを呼べる環境そのものが限られています。対応モデルは次の9つです。
claude-fable-5-1・claude-mythos-5-1claude-fable-5・claude-mythos-5claude-opus-5-5・claude-opus-5claude-sonnet-5-5・claude-sonnet-5claude-opus-4-8
利用できるプラットフォームはClaude APIとGoogle Cloudの2つです。Claude Platform on AWS・Amazon Bedrock・Microsoft Foundryではこの日付付きツールセットを使えません。Claude Managed Agentsのツールとしては提供されていません。対応外のモデルを指定した場合は、executor側のエラーではなくAPI側のinvalid_request_errorとして返ります。
まとめ
toolset_nameのechoとbrowser_stateの省略という2点は、エラー結果のテンプレートに固定しておく項目です。ここを外すとClaudeに届く前に400で止まるため、文言の工夫に進むのはその後になります。navigateのスキーム拒否とバッチの打ち切りは、この土台の上に乗る個別規約として実装します。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。