text_editorのstr_replaceで複数マッチエラーを防ぐ実装
Anthropic APIのtext_editorツールは、str_replaceが複数箇所にマッチしたときにどう止めるかをアプリケーション側の実装に委ねています。事前カウントで0件・複数件を検出する実装と、エラー後の直し方をまとめます。
str_replaceで複数マッチが起きる理由
Anthropic-schemaのtext_editorツール(str_replace_based_edit_tool)は、view / str_replace / create / insert の4コマンドでファイルを操作します。このうち str_replace は old_str に指定した文字列を new_str に置き換えるコマンドで、old_str は空白やインデントも含めて完全一致していなければなりません。
問題はここからです。old_str に指定した文字列がファイル内に2箇所以上あると、公式ドキュメントは「アプリケーションが一意のマッチを保証するか、適切なエラーを返す責任を負う」と明記しています。ツール自体は複数マッチを検出して自動で止めてくれるわけではなく、検出とエラー化はアプリケーション側の実装事項です。実装せずにそのまま置換処理を書くと、意図しない箇所まで書き換わったり、最初にマッチした1件だけが変わって残りが取り残されたりします。
text_editorツールはコードのデバッグ・リファクタリング・ドキュメント生成・テスト作成といった、Claudeがファイルへ直接手を入れる用途で使われます。提案だけでなく実際にファイルを書き換える性質上、複数マッチを検出せずに実行してしまう失敗は、提案文の間違いよりも実害が大きくなります。間違った箇所を書き換えたまま次のツール呼び出しに進み、後続の編集が誤った内容を前提に積み上がることもあります。
0件・複数件を事前カウントで検出する実装
公式ガイドが示す対処は単純です。置換の前にファイル内の出現回数を数え、1件でなければ実行せずエラーを返します。
def safe_replace(file_path, old_text, new_text):
"""Replace text only if there's exactly one match."""
with open(file_path, "r") as f:
content = f.read()
count = content.count(old_text)
if count == 0:
return "Error: No match found"
elif count > 1:
return f"Error: Found {count} matches"
else:
new_content = content.replace(old_text, new_text)
with open(file_path, "w") as f:
f.write(new_content)
return "Successfully replaced text"content.count(old_text) で先に出現回数を数え、0件なら「見つからない」、2件以上なら「何件見つかったか」を返し、ちょうど1件のときだけ content.replace() を実行します。str.replace() は本来すべてのマッチを置換する挙動ですが、事前カウントで1件だけに絞っているため実質的に単一箇所への置換になります。この関数を str_replace コマンドのハンドラに差し込めば、複数マッチと未マッチの両方を実行前に弾けます。
tool_result へ返すエラーメッセージも、公式サンプルは具体的な件数と対処を含めた文面にしています。
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: Found 3 matches for replacement text. Please provide more context to make a unique match.",
"is_error": true
}is_error: true を立てて tool_result として返すのがポイントです。例外を投げてアプリケーションを止めるのではなく、Claudeに「何件マッチしたか」と「次にどうすればよいか」を伝え、会話を継続させたままやり直させる設計です。
マッチの集計範囲は str_replace の path に指定した1ファイルの中に限られます。同名の関数が別ファイルに存在していても、それは複数マッチとしてカウントされません。複数マッチが出たときに疑うべきは他ファイルとの重複ではなく、同じファイル内でのコピーや類似コードの存在です。
複数マッチのエラーを受け取ったあとの直し方
エラーメッセージが「Please provide more context to make a unique match」と促す通り、直し方は old_str を長くして周辺行ごと指定することです。1行だけの old_str は関数内の別の場所やコピーされたコードブロックと衝突しやすく、複数マッチの主な原因になります。前後1〜2行を含めて old_str を組み立てれば、同じ1行が複数箇所にあってもブロック単位では一意になるケースがほとんどです。
このとき地味に効いてくるのが、old_str はインデントも含めて完全一致が要求される点です。タブとスペースが混在したファイルや、コピー元と貼り付け先でインデント幅が違うファイルでは、目視では同じに見える行が実際には一致せず「0件」のエラーになります。0件エラーが出たときにまず疑うべきは、対象が存在しないことよりも空白文字のズレであることのほうが多いです。
old_strを長くして一意にする具体例
「もっとコンテキストを足す」だけでは抽象的なので、具体的にどこまで長くするかを見ておきます。同じ関数がファイル内に複数回コピーされている場合、return True のような1行だけを old_str に指定すると高確率で複数マッチします。関数名を含む直前の行や、変数名を含む前後2〜3行までまとめて old_str に含めれば、行単体では重複していても塊としては一意になる可能性が大きく上がります。1行を1トークンとして数えると小さな変更に見えますが、old_str を広げる作業はエラー対応の再試行そのものであり、次のtool_use呼び出しをどこまで的確に組み立てられるかがそのまま試行回数を左右します。
undo_edit廃止でバックアップが前提になる
text_editor_20250429(Claude 4系向け)は、それ以前の text_editor_20250124 / text_editor_20241022 にあった undo_edit コマンドを廃止しました。廃止の理由は書かれておらず、ツール名だけが str_replace_based_edit_tool としてstr_replaceベースの設計を反映して改称されています。以降のバージョンに undo_edit は戻っていません。
これは複数マッチ対策と無関係ではありません。旧バージョンなら誤った置換を undo_edit で1コマンド巻き戻せましたが、現行バージョンにその手段はありません。公式のベストプラクティス集が「重要なファイルを編集させる前にバックアップを取る」実装例を置いているのは、この変更を踏まえた前提です。
def backup_file(file_path):
"""Create a backup of a file before editing."""
backup_path = f"{file_path}.backup"
if os.path.exists(file_path):
with open(file_path, "r") as src, open(backup_path, "w") as dst:
dst.write(src.read())safe_replace による事前検証は複数マッチという「実行前に防げる失敗」を止める仕組みで、backup_file は一意にマッチしたうえで内容が間違っていた場合に備える仕組みです。役割が違うため、undo手段を持たない現行バージョンではどちらか一方ではなく両方を実装しておく必要があります。
max_charactersが複数マッチ見落としを招く場合
text_editor_20250728 以降では view コマンドに max_characters パラメータを指定でき、大きなファイルを表示する際の切り詰め文字数を制御できます。ここに複数マッチ対策特有の落とし穴があります。view の出力が途中で切り詰められていると、切り詰められた範囲の外にも同じ old_str が存在する可能性に気づけません。safe_replace 側は実際のファイル全体を対象にカウントするため複数マッチはきちんと検出されますが、Claudeが old_str を組み立てる材料である view の結果が不完全だと、そもそも一意になりにくい短い old_str を選びがちになります。大きなファイルを扱う実装では、max_characters を広めに取るか、対象範囲を view_range で絞ってから編集対象を確認する運用のほうが、複数マッチの再試行回数を減らせます。
ツールバージョンによって挙動が変わる点も踏まえる
text_editorツールは2024年10月の初版以降、4つのバージョンが公開されており、str_replace を実装する上で無視できない変更が2回ありました。初版の text_editor_20241022(Claude Sonnet 3.5向け、現在は退役)と、それに続く text_editor_20250124(Claude Sonnet 3.7向け)は、view / create / str_replace / insert に加えて undo_edit コマンドを持っていました。2025年4月29日リリースの text_editor_20250429(Claude 4系向け)でこの undo_edit が廃止され、ツール名も str_replace_based_edit_tool としてstr_replaceベースの設計であることを名前に反映しました。同年7月28日の text_editor_20250728 は機能面ではほぼ同一で、大きなファイルを表示する際の切り詰め文字数を制御する max_characters パラメータが追加された点だけが差分です。
この履歴を知らずに古いブログ記事やサンプルコードを参考にすると、「undo_edit で戻せるはず」という前提のまま実装してしまいがちです。少なくとも現行のClaude 4系向けに text_editor_20250429 以降を使う実装では、undo_edit に頼れないことを前提に設計する必要があります。
File Checkpointingという代替手段もある
ここまでは素のAnthropic APIでtext_editorツールを自前実装する前提でした。Agent SDKでカスタムツールを組む場合や、ビルド済みのエージェント実行基盤を使う場合は、ファイル変更を巻き戻す手段としてFile Checkpointingが用意されています。これはtext_editorツールの undo_edit 復活ではなく、Agent SDK側がセッション単位でファイルの変更履歴を追跡する別の仕組みです。素のAPIで str_replace_based_edit_tool を直接扱う設計と、Agent SDKに乗せてFile Checkpointingに任せる設計は、同じ「編集を巻き戻したい」というニーズに対する別のレイヤーの解と考えるのが実装上は正確です。Bashツールをアプリケーションに組み込む場面でも、実行前の検証と実行後の復旧手段をセットで用意するという設計思想は共通しています。
複数マッチのやり直しはトークンコストにも跳ね返る
text_editorツールの定義自体が、通常の入出力トークンに加えて追加の入力トークンを消費します(text_editor_20250429 で700トークン)。これに加えて、複数マッチのエラーが出るたびに「エラーメッセージを読む→ より長い old_str を組み立て直す→ 再度 str_replace を呼ぶ」という往復が発生し、その都度リクエスト全体が再送されます。事前カウントで防げる複数マッチを実行時のエラーに任せてしまうと、本来不要だったこの往復の回数だけ、無駄なトークン消費が積み上がります。safe_replace のような事前検証は、安全性だけでなくこのコスト面でも実行前に弾く価値があります。
まとめ
str_replace の複数マッチは、ツールが自動で防いでくれる失敗ではなく、アプリケーション側が content.count() のような事前カウントで検出して初めて防げる失敗です。0件・複数件それぞれに具体的なエラーメッセージを返し、Claudeに old_str をやり直させる設計が公式サンプルの基本形です。text_editor_20250429 以降は undo_edit が無いため、事前検証だけでなく編集前のバックアップも合わせて実装しておくと、一意にマッチしたのに内容が想定と違っていた場合の逃げ道になります。