はじめに
「このコード、どこが問題ですか?」とAIに聞くと、根拠付きで詳細な修正案が返ってくる——そんな体験が当たり前になってきました。
しかし先日、こんな出来事がありました。あるメニュー画面の4件のバグを Claude Code に横断調査してもらい、コードの根拠を示した修正案まで受け取りました。「完璧な分析だ」と感じた直後、念のため GitHub Copilot にも同じ調査を依頼したところ、修正方針の2カ所が誤りだと指摘されました。
逆のケースもありました。外部APIとの接続URLを Copilot で調査し、Claude Code に検証を依頼したら、Copilot が古い設定ファイルを正規の設定と誤認していたことが判明しました。
興味深いのは、どちらのAIも「確信を持って」間違えていたことです。根拠ファイルのパスまで示しながら、自信満々に誤情報を返してきました。
この記事では、2つのAIツールをセカンドオピニオンとして使い分け、ハルシネーションを検出・解消する実践的なフローを解説します。
なぜ「複雑なコードベース」でハルシネーションが起きやすいか
「Javaで文字列をリストに変換するには?」のような一般的な質問であれば、LLMはほぼ正確に答えます。問題になるのは、プロジェクト固有の歴史的経緯や暗黙のルールが絡む場合です。
具体的には、次のような状況でAIは誤情報を出しやすくなります。
- 「それっぽいファイル」が複数ある: 本番設定・旧設定・ソースツリー内の開発時設定が混在し、AIが「より近くにある」ファイルを優先してしまう
- 暗黙の連携がある: 「このHTML要素のIDは別のJSファイルがすでに管理している」という文脈が読めない
- 移行済みの設定が残っている: 「このファイルはリポジトリ整理後に使わなくなった」というコンテキストをAIは持てない
- 「削除 vs コメントアウト」のような意図の問題: 技術的にどちらも動くが、チームの慣習として正しい方がある
要するに、LLMはコードの構造は読めても、コードの歴史と意図は読み間違えやすいのです。これがレガシーシステムや大規模リポジトリでのハルシネーション頻度が上がる理由です。
ツール特性の比較:Claude Code と GitHub Copilot
2つのツールには「コードへの入り口」の違いがあります。
| Claude Code | GitHub Copilot(Agent) | |
|---|---|---|
| 調査の起点 | ローカルのファイルシステムを直接探索 | GitHubリポジトリ(リモート)のコードを検索 |
| 強み | デプロイ設定・ローカル限定のファイルにもアクセス可能。grep で全環境の設定値を横断比較できる | PR・ブランチ・コミット履歴との連携が自然。「このブランチに既に修正がある」の検出が得意 |
| 弱み | ファイル数が多いと「それっぽいファイル」を選びやすくなる | リポジトリ外(例:別管理の設定リポジトリ)にはアクセスしにくい |
この特性の違いが、後述する「どちらが間違えるか」の方向性を決めます。
重要なのは「どちらが優れているか」ではありません。それぞれが異なる入り口からコードを見ているため、片方が見落とすものをもう片方が拾いやすいという非対称性こそが価値になります。
実践例①:Claude Code が誤り、Copilot が正解したバグ調査
状況
サービス追加申込画面で、過去の修正チケットの影響によるデグレードと思われる4件のバグが発覚しました。
- Bug 1: 特定商品の重複申込確認ダイアログが表示されない
- Bug 2: 特定セクションに一括申込ボタンが表示されない
- Bug 3: 一括申込ボタンのマウスオーバー画像が壊れる
- Bug 4: 特定商品がメニューに表示されない
4件すべてが同一チケットの修正コードの影響範囲に収まっており、複数の JSP・JS ファイルにまたがる横断的な調査が必要でした。
Claude Code による初期調査
Claude Code に4件のバグを依頼し、各バグの原因コード箇所・修正案・根拠ファイルをセットで提示してもらいました。
分析結果は詳細で筋も通っていましたが、その中で注目すべき2つの修正案がありました。
Bug 2の修正案(Claude Code)— 新規IDを作成して追加する
<!-- Claude Codeの提案 -->
<span id="bulk_apply_btn_4_active" class="js_hide">
<a href="..."><img src=".../btn_bulk_apply.png"></a>
</span>
<span id="bulk_apply_btn_4_inactive" class="js_hide">
<img src=".../btn_bulk_apply_off.png">
</span>
Bug 4の修正案(Claude Code)— <c:if> タグを削除する
- <c:if test="${status.index >= 3}">
...表示制御ロジック...
- </c:if>
どちらも根拠付きで、読んだ限りでは問題なさそうです。しかし、ここに落とし穴がありました。
Claude Code が見落としていた文脈:
- Bug 2: 既存の
form-config.jsがすでにbulk_apply_btn_2_active/inactive(id2)を制御している。id4 という新規IDを作っても JS 側が反応しないため、ボタンは制御されない - Bug 4: このチームの慣習として、ロジックの「削除」ではなく「コメントアウトによる無効化」が標準。将来の参照や rollback を容易にするための判断
Claude Code はファイルを単体で読んで最適な回答を出したのですが、「別ファイルとの連携」と「チームの歴史的な慣習」という2つのコンテキストが抜け落ちていました。
Copilot へのセカンドオピニオン依頼
Claude Code の調査結果を GitHub Copilot に転記し、比較を依頼しました。
「Claude Code の調査結果を転記します。これまでの調査結果と比較して、差異がある場合はどちらが正しいか調査してください。」
Copilot はブランチ・コミット履歴を参照しながら、次のように指摘しました。
Bug 2(Copilot)— 既存IDを再利用する
<!-- Copilotの指摘: id2(既存)を使うべき -->
<span id="bulk_apply_btn_2_active" class="js_hide">
<a href="..."><img src=".../btn_bulk_apply.png"></a>
</span>
<span id="bulk_apply_btn_2_inactive" class="js_hide">
<img src=".../btn_bulk_apply_off.png">
</span>
form-config.js はすでに bulk_apply_btn_2_active/inactive を制御している。id2 を再利用すれば JS の変更は不要。
Bug 4(Copilot)— タグはコメントアウトにとどめる
- <c:if test="${status.index >= 3}">
+ <!-- <c:if test="${status.index >= 3}"> -->
...表示制御ロジック...
- </c:if>
+ <!-- </c:if> -->
コミット履歴を参照すると、過去の修正でも同様の条件制御はコメントアウトで無効化されていた。
加えて Copilot は、「Bug 2 と Bug 4 は過去の修正ブランチにすでに fix が含まれているのでは?」という追加の仮説も提示してきました。
git diff で裁定
2つのAIの主張が食い違ったため、実際の git diff で確認します。
git diff HEAD remotes/origin/feature/[過去の修正ブランチ]
差分を確認したところ、Copilot の指摘は両方とも正しいことが確定しました。Claude Code も再確認後「重大な差異が2点あります。Copilot の指摘が正しい」と認めています。
さらに Copilot の「過去ブランチに fix が含まれる」という仮説も git diff で確認できましたが、ここからさらに深掘りすると意外な事実が判明します。「修正済みブランチ」のマージが、逆に今回の4件のバグを全て導入した原因だったのです。この発見も Copilot による「ブランチとの差分」という視点がなければ気づけませんでした。
最終的には新規 hotfix ブランチを切り、3ファイルの修正でコミットに至りました。
この事例から学んだこと
Claude Code が提案した「新規 ID」は技術的に間違いではありません。しかし、既存の JS ファイルとの連携という暗黙の文脈を読み切れていなかったのです。
Copilot はコミット履歴を起点に調査を進めるため、「このパターンは過去にどう扱われてきたか」という歴史的文脈を掴みやすい。これが、今回 Copilot が正解した理由でした。
実践例②:Copilot が誤り、Claude Code が正解した API 仕様調査
状況
外部システムとの連携 API の仕様書を作成するにあたり、接続先 URL やポート番号をコードベースから確認する必要がありました。このプロジェクトは本番・検証・テストの複数環境があり、さらに過去の設定管理方式の変更により、同名の設定ファイルが複数のパスに存在する状態でした。
2つのAIが異なる値を返した
Claude Code がローカルのデプロイ設定ファイルを参照した結果:
# Claude Codeの調査結果
external_api.url.base=https://[外部APIサーバ]/cc
ポート番号の指定なし。
一方、Copilot に同じ調査を依頼すると:
# Copilotの調査結果
external_api.url.base=https://[外部APIサーバ]:[ポート番号]/cc
ポート :[ポート番号] が付いている。
仕様書に記載する値なので、どちらが正しいかは重要な問題です。
全ファイル grep で裁定
設定ファイルをリポジトリ全体で網羅的に調べました。
grep -r "external_api.url.base" [デプロイ設定ディレクトリ]/
全環境の設定値を確認した結果:
| 環境 | 設定値 |
|---|---|
| 本番・検証・テスト(全実環境) |
https://[外部APIサーバ]/cc(ポートなし) |
| 検証・テスト(一部モック環境) |
https://[モックサーバ]/api/cc(実APIには未接続) |
Claude Code の回答が正しいことが確定しました。では、Copilot はどのファイルを参照していたのでしょうか。
Copilot が間違えた理由を掘り下げる
調査を続けると、Copilot はソースツリー内の別パスにある設定ファイルを参照していたことがわかりました。
[ソースツリー内の旧設定ファイルパス]
→ external_api.url.base=https://[外部APIサーバ]:[ポート番号]/cc
このファイルの素性を確認します。
- ビルド設定に組み込まれていない: JAR/WAR の成果物に含まれないため、実際にはデプロイされていない
- 移行済みメモが存在する: 設定ファイルを専用の設定リポジトリに移行した際に残置されたもの
- 本番環境用の設定が同ディレクトリに存在しない: 移行途中の痕跡
結論: Copilot が参照していたのは、設定管理の整理前に使われていた旧設定ファイルでした。ソースツリーの「それっぽいパスにある設定ファイル」を正規の設定と誤認したのです。
Copilot はリポジトリのソースツリーを検索するため、「パス名と内容が一致するファイル」を優先しやすい特性があります。「設定ファイルの管理場所が移動した」という文脈を持てないと、今回のような誤認が起きます。一方 Claude Code は、ローカルのデプロイ設定を直接参照できたため、全環境の設定値を正確に比較できました。
「AI同士を戦わせる」検証フロー
2つの実践例から得た、再現性のある4ステップフローです。
Step 1 — AI-A で初期調査
Claude Code または Copilot のどちらか使いやすい方で調査を行います。調査結果は根拠ファイル・行番号とセットで出力させると後工程で使いやすくなります。
Step 2 — AI-B にセカンドオピニオンを依頼
Step 1 の結果を別のAIに転記し、次のプロンプトで比較させます。
「[AI-A] の調査結果を転記します。
これまでの調査結果と比較して、差異がある場合は
どちらが正しいか調査してください。」
「差異がある場合は調査せよ」がポイントです。単に「確認して」と伝えると一致した部分しか返ってきません。能動的に差異を探させることで、AIが見逃しを指摘しやすくなります。
Step 3 — 差異があれば根拠ファイルまで掘り下げる
2つのAIが食い違ったとき、「どちらが正しいか」だけでなく「なぜ食い違ったのか、根拠ファイルを示して」と深掘りを求めます。今回の Copilot の誤りは、「どのファイルを見ていたか」を掘り下げることで旧設定ファイルの混在という根本原因が明らかになりました。
Step 4 — grep / ファイル直読み / git diff で最終確認
どちらかの主張に傾いたら、コード(事実)で最終確認します。AIの「説明の論理的な正しさ」と「内容の事実としての正しさ」は別物です。
# 設定値を全環境横断で比較
grep -r "設定キー名" [設定ディレクトリ]/
# 変更の起点となったコミットを特定
git diff HEAD [問題のブランチ]
# ファイルの変更履歴を辿る
git log --oneline [ファイルパス]
補足:2つのAIが一致しても油断しない
「2つのAIが同じことを言っている → 正しい」は成立しません。同じコードベースを参照している以上、同じバイアスを持つ可能性があります。重要な判断については、一致していても最終的にコードで確認する習慣を持つことが大切です。
真理の拠り所は git diff と生ファイル
AIは「それっぽい答え」を確信を持って返します。コードは嘘をつきません。
| AIの回答 | コード・git | |
|---|---|---|
| 正確性 | 高い確率で正しいが、間違いでも自信満々 | 現在の状態を常に正確に反映 |
| 文脈の把握 | 推測が混じる | 事実のみ |
| 歴史的経緯 | 見落とすことが多い |
git log / git blame で追跡可能 |
| 速度 | 秒単位 | コマンド実行が必要 |
AIを使う意義は「速度と仮説生成」にあります。そして grep や git は「検証」に使います。AIの回答を「仮説」として受け取り、コードで「検証」するという役割分担が、AI活用調査の基本スタンスです。
まとめ
2つの実践例を通じて見えてきた、AI を使ったコード調査の核心を整理します。
- どちらのAIが優れているかではない: Claude Code と Copilot はそれぞれ異なる切り口でコードを読む。今回の2事例では「誤る方向」が逆だった——これが最も示唆的な事実です
- セカンドオピニオンは最も安価な検証手段: 1つのAIに深掘りを続けるより、別のAIに転記して比較させる方が盲点を発見しやすく、時間コストも低い
- 差異が見つかった瞬間が最も価値ある: 2つのAIが食い違ったとき、そこには必ず「どちらかが見落としているコンテキスト」がある。根拠まで掘り下げる価値がある
- 最終確認はコードで: どれだけ論理的な説明でも、grep とファイル直読みで確認するまでは「仮説」に留めておく
AIによるコード解析は、単独で使うと「AIが言ったから大丈夫」という過信を生みやすい罠があります。複数AIの比較とコードによる最終確認を組み合わせることで、はじめて「確信できる調査」になります。
AIなしの調査に比べた生産性向上は圧倒的です。その恩恵を最大化するには、AIを使いこなしながらAIを疑う習慣——この一見矛盾した姿勢こそが、AI時代のエンジニアリングの要諦だと感じています。
本記事の事例は実際の業務調査をもとにしていますが、固有名詞・IPアドレス・社内情報は一般化・マスクして記載しています。