AIに「修正して」と言っている限りバグは直らない

はじめに
「修正して」はデバッグではない。ただのランダム探索だ。
AIはコードを修正した。テストを実行した。また落ちた。このループから抜け出すには、問いかけの設計を変える必要があります。
AIにテストを直させていて、こんなことありませんか?
AIが提案するコードは一見正しい。しかし実行するたびに別のエラーが出る。
「今度こそ」と思いながら同じ会話を繰り返し、気づけば2時間が消えていた。
AIは嘘をついていない。ただ、あなたが「正しい問いを与えていない」だけです。
この記事をお勧めしない人
- AIへの問いかけ方を変えるより、コードを直し続ける方が早いと考えている人
- テストが落ちたら、まずテストファイルを見直すのが正しいと思っている人
- AIにメモリやルールを設定すれば、あとは任せておけると考えている人
当てはまるなら、この記事は必要ない。
AIが消費し続けるあなたの時間
- 「テストコードを直せ」という指示を受けたAIは、アプリ側の500エラーを見ようとしない。
- メモリに保存したデバッグ戦略は、オペレーターが明示しない限りAIの中で眠り続ける。
- 6回目の対症療法が終わった頃、問題の根本はまだ手つかずのまま残っている。
「動く実装との比較」がある未来
- 「blog(動く)とlabo(壊れている)の差分を見せて」という一言で、AIが根本原因に辿り着く未来があります。
- 2時間かかっていた問題が25分で解決できる、問いかけ設計の地図を手に入れられます。
- この手法は、筆者が運営するReact Router v7 × Cloudflare Edgeのブログで実際に発生した問題で検証済みです。
- この知見は、AIツールの公式ドキュメントには書かれていない、実戦から得た問いかけ設計の実践知です。
私も同じでした
筆者も「修正して」を繰り返し、同じ問題に昨日2時間費やした経験があります。
この記事では、その翌日に25分で解決した問いかけ設計の考え方と、明日から試せる具体的な手順を持ち帰れるように書きました。
原因の深掘りと実装レベルの詳細は、続きで確認できます。
📝 概要
新テーマ(labo)を追加したとき、E2Eテストが失敗しました。「修正して」とAIに依頼を繰り返し、2時間が経過しても解決しなかったです。
翌日、同じ問題を「動く実装との比較を起点にする」という問いかけ設計に変えたところ、25分で解決しました。
なぜ問いかけ方を変えるだけでここまで結果が変わるのか、その構造的な理由を解説します。
発生環境
- フレームワーク : React Router v7
- ホスティング : Cloudflare Pages/Workers
- テストツール : Playwright
- AI : Claude Code
なぜ「修正して」では直らないのか
AIの局所修正バイアスと、E2Eテストをシステムとして見ない視点の欠落が組み合わさったことが原因でした。
最悪の問いかけ
まず、やってはいけないパターンを明示します。
「テストが落ちてる。直して」この一言でAIに何が起きるか。
- AIはテストコードを「正」とみなし、テストに合わせてアプリを直そうとする
- アプリ側の破損(500エラー等)を調査対象から外す
- 症状(セレクターが見つからない等)に対するパッチを量産する
結果:【根本原因に一切近づかない。】
読者の多くがこれをやっています。筆者も昨日やりました。
AIの「局所修正バイアス」とは
AIは与えられた問題を最短距離で解こうとします。「テストが落ちている」と伝えると、AIはテストコードを修正対象として認識し、アプリ側(ルートの実装、データローダー等)を調査しません。
これは「失敗している仮説」が前提になってしまう構造です。
「テストを直して」→ AI: テストファイルを修正
↓
テストは修正されたが、アプリは壊れたまま
↓
テストを実行 → 別のエラーで再度失敗AIが出力するコードは正しいです。しかし「何を直すべきか」という前提が間違っている。
メモリに保存しても自発的には使わない
今回の事例でさらに興味深いのは、デバッグ戦略(「動く実装との比較を起点にする」)をメモリ保存済みだったにもかかわらず、AIは自発的に適用しなかった点です。
メモリ保存: "動く実装との比較を起点にする"
↓
しかし「修正して」という指示を受けたAIは
メモリを参照せず、局所的な問題解決を試みる「設定すれば自律的に動く」という期待は、現時点のAIには当てはまりません。【オペレーターが明示的に戦略を宣言して初めてメモリは機能します。】
エラーメッセージが根本原因を隠す
Error: expect(locator).toBeVisible() failed
Locator: getByTestId('posts-section')
Expected: visible
Timeout: 5000msこのエラーを見ると「テストコードが間違ったセレクターを使っている」と判断しがちです。しかし実際の原因は /labo ルートが HTTP 500 を返していたため、posts-section 要素が DOM に存在しなかったことでした。
エラーメッセージは「症状」を示しますが「根本原因」を示しません。AIは症状に対して最短で回答を出します。【結果として、症状を直すコードが量産され、根本原因は未発見のまま残ります。】
「動く実装との比較」が全てを変えた
2時間の試行錯誤を翌日25分で解決した一言がこれです。
「blog(動く)と labo(壊れている)の差分を見せて」
なぜこれで解決するのか
比較は「探索範囲」を強制的に制限します。
「修正して」と伝えたとき、AIの探索空間は無限です。テストコード・アプリコード・設定ファイル・型定義、どこを直せばいいか分からないまま、確率的に候補を出し続けます。
比較軸を与えると、正解(blog)が固定されます。AIの探索空間が「差分だけ」に圧縮される。
従来:「修正して」 → 探索空間: 無限(全コードが対象)
比較:「差分を見せて」 → 探索空間: 差分のみ(正解が固定される)blog ルートと labo ルートのファイルを並べた結果、即座に3箇所の spec 参照ミスが発見されました。差分を出した時点で、原因候補は数個に絞られた。25分という数字は運ではなく、探索空間の圧縮がもたらした必然です。
コピペで使えるプロンプト
「差分を見せて」だけでは抽象的すぎます。以下をそのままコピーして使ってください。
以下の2つを比較して、動作の違いと原因になりうる差分を特定してください。
- 正常に動作している実装: [動いている機能のパス / ルート名]
- エラーが発生している実装: [壊れている機能のパス / ルート名]
観点:
- ルーティング
- データ取得(loader / API)
- コンポーネント構造
- spec / 設定参照
最小の差分で説明してください。「正常に動作している実装」と「エラーが発生している実装」を具体的に指定することがポイントです。[] の部分をあなたの環境のパスに書き換えるだけで使えます。
根本原因: spec 参照ミス3箇所
実際に見つかった原因は、存在しない spec を参照している3箇所でした。
- const postsSpec = await loadSpec("blog/labo-posts"); // labo-posts は存在しない
+ const postsSpec = await loadSpec("blog/posts"); // 正しい参照blog ルートを確認すれば即座に分かる差分でした。しかし「修正して」という指示ではこの比較が行われなかったのです。
AIデバッグの問いかけ設計 2原則
今回の体験から、AIへの問いかけ設計として以下の2原則を得ました。
原則1: 「動くものと動かないものの差分」を起点にする
❌ 弱い問いかけ: 「このテストが落ちている。直して」
✅ 強い問いかけ: 「blog(動く)と labo(壊れている)の実装差分を見せて」比較軸があると、AIは根本原因を探すモードに切り替わります。
原則2: メモリは宣言して初めて機能する
今回最も重要な発見です。AIにルールを記憶させるだけでは不十分です。「既存の動く実装を参照点にして」と問いかけの中で明示することがトリガーになります。
❌ 期待していたこと: メモリに保存 → AIが自律的に適用する
✅ 実際の動き: メモリに保存 + 明示的な宣言 → AIが適用するこの2原則はE2Eテストに限りません。「動く実装」が存在するすべてのデバッグ——APIバグ、フロント不具合、インフラ差分——に適用できます。唯一の条件は「比較対象が存在すること」だけです。
まとめ: AIは賢くない、探索してるだけ
デバッグは修正じゃない。探索空間の設計だ。
AIは問いを解釈し、探索空間の中で最短距離の答えを返します。賢いのではなく、効率的に探索しているだけです。
「修正して」という問いは、探索空間を無限に広げます。
「差分を見せて」という問いは、探索空間を差分だけに圧縮します。
【お前が探索空間を設計しろ。】
AIの生産性は、AIの賢さではなく、あなたの問いかけ設計で決まります。
次にバグが出たら「修正して」と言うな。まず比較対象を用意しろ。
