Claude Code で Edit ツールを使い始めて数日経つと、ほぼ全員が一度は遭遇するのが Error: old_string is not unique in the file というエラーです。3 行ほどのコード片を渡しても、ファイル内の同じパターンが複数あるとツール呼び出しが弾かれて、Claude が再試行を繰り返してトークンが嵩む——そんな経験はないでしょうか。
私自身、4 サイト並行運用のコード生成パイプラインを Claude Code に任せている関係で、このエラーで詰まる頻度はかなり高い時期がありました。最終的に「ツールが厳格すぎる」のではなく「Claude に渡すコンテキスト設計が甘かった」という結論に落ち着きましたので、その学びを共有します。
なぜ「not unique」が出るのか
Edit ツールはファイル内の old_string を 1 箇所だけ書き換えます。replace_all: true を指定しない限り、対象が複数見つかれば「どの 1 箇所か特定できない」と判断してエラーを返します。これは仕様上の安全装置で、似たコード片を誤って全て置換してしまう事故を防ぐためのものです。
つまりエラーの本質はツール側のバグではなく、Claude が選んだ old_string がファイル内で一意ではなかった、というコンテキスト判断の失敗です。多くの場合は次のいずれかに当てはまります。
- 似たような
if文やconsole.logが複数あり、行単位で見ると区別できない - import 文や useState の初期化など、定型的な記述で同じ形が連続している
- ファイル冒頭のフロントマターやコメントブロックに同じ語句が並んでいる
replace_allを意図したのにfalse(デフォルト)のまま呼び出してしまった
ですので「old_string をそのまま長くする」「前後の文脈を含めて一意になる範囲を渡す」が基本対処になります。
対処パターン①: 前後 1〜2 行を含めて一意化する
最もシンプルで効くのが、編集対象の行の上下を含めて一意なブロックにする書き換え戦略です。たとえば次のように同じ setLoading(false) が複数ある場合を考えてみます。
// 修正したい箇所(上)
if (response.ok) {
setData(await response.json());
setLoading(false);
}
// 別の場所
if (cached) {
setData(cached);
setLoading(false);
}setLoading(false) だけを old_string にすると当然 not unique で弾かれます。前後を含めて、対象ブロック全体を渡すのが正解です。
// old_string(一意化されたブロック)
if (response.ok) {
setData(await response.json());
setLoading(false);
}
// new_string
if (response.ok) {
setData(await response.json());
setLastFetched(Date.now());
setLoading(false);
}この書き方であれば、よほど特殊な構造でない限りファイル内で 1 箇所しか一致しません。Claude にこの戦略を意識させたい場合は CLAUDE.md に「Edit する際は前後 1〜2 行を必ず含めて一意化する」と書いておくと再発が劇的に減ります。
対処パターン②: replace_all を使うべき場面を見極める
変数名の一括リネームや、import パスの全置換のように「ファイル内で同じ文字列を全部書き換えたい」ケースでは replace_all: true の指定が正解です。これを Edit ツールに伝えるには、Claude のプロンプトの中で明示する必要があります。
// Claude に依頼するときの例
// 「次のファイルで `oldVarName` をすべて `newVarName` にリネームしてください。
// replace_all: true を指定して 1 回の Edit で完了させてください」注意点として、replace_all は副作用が大きいので、文字列リテラル内の偶然の一致まで含めて全置換してしまいます。たとえばコメント内の // TODO: oldVarName を直す のような部分も書き換わってしまうので、変数名のように偶発的な衝突がほぼないトークンにだけ使うのが安全です。コードのリファクタリング時は grep -n 等で事前に出現箇所を確認しておくと安心です。
対処パターン③: 一度では済まないときは Edit を分割する
似た構造のブロックが何箇所もあって、それぞれを別の内容に書き換えたい場合は、Edit を複数回に分けるのが王道です。1 回で済ませようとして「正規表現で柔軟に置換してくれないか」と Claude に頼んでも、Edit ツールは厳密一致しか受け付けないのでうまくいきません。
実用的には、次のような順序でプロンプトを設計するとミスが減ります。
- 該当ファイル全体を
Readで読み込み、何箇所該当パターンがあるか把握する - 上から順に「ブロック A」「ブロック B」と命名し、それぞれの周辺コードを含めた一意な
old_stringを作る - それぞれを順番に
Editツールで更新する(途中で失敗したらその時点で停止して原因を確認)
この設計を「変更計画」として最初に Claude に提示しておくと、エージェント側の試行錯誤が大幅に減ります。私は CLAUDE.md に「複数箇所を変更するときは、まず変更計画を箇条書きで提示してから Edit を順番に実行する」と書いており、これだけで not unique エラーが体感で 8 割減りました。
対処パターン④: 大規模書き換えなら Write を選ぶ
ファイルの 30% 以上を書き換えるような大規模リファクタリングでは、Edit を何度も呼ぶより Write でファイル全体を書き直す方が早く、エラーも起きません。Edit は局所的な修正のためのツールであり、構造を変える書き換えには向かないからです。
判断の目安としては、次のように考えるとよいでしょう。
- 行数の変化が ±10 行以内 →
Editで問題なし - 関数 1 つを丸ごと書き換える →
Editでも可だが、対象関数全体をold_stringにする - ファイル構造を変える(関数追加・削除・順序入れ替え)→
Writeで書き直す
Write を使う場合は、事前に必ず Read で現在の内容を確認するのが大前提です。Claude Code の仕様上、Write の前に Read していないとツール呼び出し自体が失敗するようになっています。
ファイルサイズが大きすぎる場合の構造的アプローチ
同じファイルでこのエラーが繰り返し発生する場合は、ファイル自体が大きくなりすぎているサインかもしれません。TypeScript や Python のファイルが 800〜1,000 行を超えてくると、似たパターンが統計的に避けられなくなり、短い old_string で一意化することがほぼ不可能になります。この場合の本質的な解決策はツールではなくコード構造側にあって、ファイルを小さなモジュールに分割する、責務を分ける、共通ロジックをヘルパー関数に抽出する、といった対応が効きます。ファイルサイズが下がれば、自然と一意化に必要なコンテキストの範囲も狭くなり、Edit エラーは静かに消えていきます。
関連する小ワザとして、長いファイルの冒頭に「セクション目次」を 1〜2 行のコメントで残しておくと、Claude が目印として活用してくれます。例: // 1) 認証ハンドラ 2) キャッシュ ユーティリティ 3) HTTP クライアント のように書いておけば、ファイル全体を走査せずに目的のセクションだけを参照してくれるので、結果的に Edit の精度も上がります。
エラーが出たときの即応手順
実際にエラーが出てしまったときは、次の順で対応するのが最短です。
# Step 1: 該当パターンの出現箇所を確認
grep -n "対象の文字列" path/to/file.ts
# Step 2: 出現箇所が 2 箇所以上あれば、前後を含めた一意なブロックを作る
sed -n '10,20p' path/to/file.ts # 該当行の周辺を表示
# Step 3: Claude に「Edit のとき old_string に上下 2 行を含めて」と再依頼Claude Code の対話画面でやり直す場合、grep の結果を Claude に貼り付けて「この出現箇所のうち X 行目を編集したい。前後 2 行を含めた一意な old_string を作って Edit してほしい」と依頼すると、ほぼ確実に成功します。エラーメッセージだけを Claude に渡しても、ファイルの構造を改めて読まずに勘で再試行してしまうことがあるので、必ず grep の出力をセットで渡すのがコツです。
他の AI エディタでも応用が効く話
ひとつ安心材料を挙げると、このタイプのエラーは Claude Code 固有のものではありません。Cursor、Aider、その他のエージェント型エディタも、in-place な編集時には何らかの「一意性保証」のセーフティを持っています。エラーメッセージの文言は違っても、根本にある制約は同じで、自動編集には「ファイル内で確実に特定できるアンカー」が必要というだけのことです。ですから今回紹介した習慣(コンテキストで囲んだ old_string、複数箇所変更前の計画提示、構造変更時は Write へ切り替え)は、他のツールに移ってもそのまま通用します。一度身につける価値のあるスキルです。
全体を振り返って
old_string is not unique エラーは、Edit ツールの厳しさではなく、私たち(あるいは Claude)が「ファイル内で一意な範囲」を意識していなかったサインです。今日からできる対策はとてもシンプルで、編集対象の上下 1〜2 行を必ず含めて old_string を作る、これだけでほとんどのケースが解決します。Claude Code を腰を据えて使い込んでいる方には、CLAUDE.md にこのルールを 1 行追加することをおすすめします。エラーで足を取られる時間が減って、本来やりたかったコード設計に集中できるようになります。
Claude Code の使いこなし
エラーの正体は「old_string が完全一致しない」だけ
Edit ツールの内部は意外なほどシンプルで、old_string をファイル本文の中から探して、見つかったら new_string に置換するという1行に尽きます。完全一致が前提なので、空白1個・改行コード1つ違うだけで not found が返ります。
# Edit ツールが内部的にやっていること(イメージ)
if [[ "$file_content" == *"$old_string"* ]]; then
# 置換する
else
echo "Error: String to replace not found in file"
fiつまり、原因は次の3つのどれかに必ず収まります。
- ファイル側が外部要因で変わっている(保存時の自動整形・git pull・他のエディタ)
- Read で取得した内容を Claude が「圧縮」して引用してしまった
- 不可視文字や改行コードの違いがある
順番に見ていきます。
ケース1: 保存時の自動整形でインデントが変わっている
最も多いのがこれです。VS Code の editor.formatOnSave や Prettier、ESLint の --fix、Python の Black、Go の gofmt など、保存と同時にファイルを書き換えるツールが裏で動いていると、Claude が Read した直後にファイルが別物になります。
たとえば Claude は次のようなコードを Read したとします。
function calculateTotal(items: Item[]) {
return items.reduce((sum, item) => sum + item.price, 0);
}ところが、保存時に Prettier が走ってインデントが4スペースから2スペースに変換されると、ディスク上のファイルはこうなっています。
function calculateTotal(items: Item[]) {
return items.reduce((sum, item) => sum + item.price, 0);
}Claude が「4スペースのインデントの行」を old_string に指定しても、もうファイル中に存在しないため not found になります。
解決策
最もシンプルな解決は、Claude に再度 Read を呼ばせてから Edit させることです。/clear で文脈をリセットすると確実ですが、私はもっと軽い方法を好みます。
ファイルが保存時に整形されている可能性があるので、Read で最新の内容を取得し直してから編集してください。このように一言伝えるだけで、Claude は素直に Read からやり直してくれます。長期的には、作業中のリポジトリでは editor.formatOnSave を一時的に無効化するか、Claude Code 専用のプロファイルを VS Code に作ってフォーマッターを切るのが安定します。
ケース2: タブとスペースが視覚的に同じに見える
エディタの設定で「タブを4スペースで表示」している場合、ファイル中のタブ文字とスペース4個は人間の目には完全に同じです。Claude が Read で取得する内容にはタブ文字(\t)が含まれていても、こちらが「4スペースに見えるからスペースだろう」と思い込んでプロンプトを書くと、Claude も同じ思い込みをしてしまうことがあります。
これを切り分けるには、ファイルを cat -A で開くと一目瞭然です。
$ cat -A src/utils.ts | head -3
function calculate() {$
^Ireturn 42;$
}$^I がタブ、$ が行末です。タブ・スペースの混在が原因の場合、Claude に「このファイルはインデントにタブを使っています」と一言補足するだけで、Edit が一発で通るようになります。プロジェクトに .editorconfig を置いておくと、Claude がファイルを開いた時点でインデントスタイルを自動で認識してくれるので、長く使うリポジトリでは導入をおすすめします。
ケース3: 改行コード(CRLF と LF)の混在
Windows で開発しているチームから受け取ったコードや、git 設定が崩れているリポジトリでは、ファイルの改行コードが CRLF(\r\n)と LF(\n)で混在していることがあります。Claude は基本的に LF で正規化して扱おうとするので、CRLF のファイルに対する Edit が通らなくなることがあるのです。
確認は次のコマンドで行えます。
$ file src/components/Button.tsx
src/components/Button.tsx: ASCII text, with CRLF line terminatorswith CRLF line terminators と出たら CRLF です。git の設定で全社的に LF に統一する場合は、リポジトリのルートに .gitattributes を置きます。
* text=auto eol=lf
そして既存ファイルを変換します。
git rm --cached -r .
git reset --hard
# あるいは個別に
dos2unix src/components/*.tsx私自身、Windows と macOS が混在するチームのプロジェクトで何度もこれにハマりました。一度 .gitattributes で揃えてしまえば、以後 Claude Code の Edit が安定します。
ケース4: 不可視文字(ゼロ幅スペース・BOM)が混入している
これは比較的レアですが、本当に原因が分からないときに疑うべきポイントです。Web からコピペしたコードや、Markdown を経由して貼り付けた文字列には、ゼロ幅スペース(U+200B)や非破壊スペース(U+00A0)といった不可視文字が混じっていることがあります。
# 不可視文字の検出
$ grep -P "[\x{00A0}\x{200B}-\x{200D}\x{FEFF}]" src/utils.tsヒットした場合は sed で一括除去できます。
sed -i 's/\xc2\xa0/ /g; s/\xe2\x80\x8b//g' src/utils.tsUTF-8 BOM が頭についているファイルも要注意で、これは次のコマンドで剥がせます。
sed -i '1s/^\xEF\xBB\xBF//' src/utils.ts私は新しく外部からファイルを取り込むときは、必ず file コマンドで BOM の有無と改行コードを確認するようにしています。
ケース5: Claude が「省略記号」で Edit しようとしている
長いファイルを編集するとき、Claude が // ...existing code... のような省略記法を old_string に含めてしまうことがあります。これは絶対に動きません。old_string はファイル中に物理的に存在する文字列でなければならず、Claude の頭の中だけにある「中略」記号は当然マッチしません。
このパターンは、Claude に次のように指示すれば防げます。
Edit の old_string には省略記法(// ...existing code...)を絶対に使わず、ファイル中に実在する文字列を完全一致でコピーしてください。それでも頻発する場合は、編集対象の関数やブロックを丸ごと old_string に入れさせるよう促すと安定します。短い old_string よりも、関数全体や数行のかたまりのほうが、結果的にユニーク性が高くなりエラーが減るのは経験則です。
それでも直らないときの最終手段
ここまでの対処で90%以上は解決するはずですが、それでも失敗が続く場合は次の順で試します。
まず Claude に /clear で文脈をリセットしてもらい、Read から完全にやり直します。それでもダメなら Write ツールでファイル全体を書き直してもらう方法に切り替えます。Edit が10回失敗するくらいなら、Write で一発書き直したほうが時間も精度も勝ります。
ファイル自体が壊れている疑いがあるときは、Claude Code の /doctor コマンドで自己診断する方法を実行すると、設定や権限まわりの問題も含めて検出してくれます。Edit が連鎖的に失敗する状況では、Bash ツールの実行エラーが起きるパターンと原因が共通していることもあるので、合わせて確認すると早く切り分けられます。
書籍で背景まで踏み込んで
次の一歩
今日の記事で紹介した3つのケース(自動整形・改行コード・不可視文字)のいずれかで困っている人は、まず cat -A ファイル名 | head -20 を1回叩いてみてください。それだけで原因の8割は見えるようになります。Claude Code の挙動を「壊れた」と感じる前に、ファイル側で何が起きているかを覗く習慣がつけば、Edit ツールはとても頼れる相棒に戻ります。
エディタの保存時整形と Claude Code の付き合い方は、チームの開発体験に直結する地味で重要なテーマです。私自身もまだ最適解を探りながらやっていますが、.editorconfig と .gitattributes の2ファイルをきちんと整備するだけで、Edit の失敗頻度が体感で半分以下になりました。明日のあなたの作業が、少しでも気持ちよく流れていくことを願っています。