Claude Codeの「テストPASS」報告は何を保証するのか — 別セッションのレビュー13件との比較
Claude Codeにプログラムの開発を任せ、百件以上のテストがすべてPASSしたと報告されたら、その品質をどこまで信用できるだろうか。
Claude Codeのクラウド環境で実施した一連の開発実験のうち、開発担当とは別のセッションがコードをレビューした13組を調べた。その結果、12組で新たな問題が指摘された。開発側が合格を報告したことを確認できた12組に絞ると、11組で追加の指摘があった。
もちろん、AIの開発が12組で失敗したという意味ではない。実際に動作するプログラムを完成させた後でも、想定外の入力や処理性能、修正による副作用など、別の視点で初めて見つかる問題があった。指摘には、再現手順つきの不具合から、軽微な挙動の変化や懸念まで幅がある。
本記事では、具体的な実験結果を紹介しながら、AIのテストPASS報告をどう評価し、実務でどのようにレビューすべきかを考える。なお、これは欠陥を探す目的で設計したレビューの結果であり、「AIの自己評価は信用できない」という一般論の証拠ではない。何が見つかり、何が見つからず、どこまで言えるのかを区別して書く。
検証したかったこと
- AIが「テストPASS」と報告したら信用できるか。
- 独立したレビューで何が新たに見つかったか。
- 自己レビューと別セッションのレビューに違いはあるか。
- テスト件数が多ければ品質を保証できるか。
- 実務ではどのようなレビュー体制が合理的か。
実験環境と評価方法
実験はClaude Codeのクラウドセッション上で行い、モデルは主に claude-sonnet-5-5 である。人間は起動後に介入していない(各実験の記録にある自己申告)。対象は自作のCLIツールから、実在OSSの修正まで幅がある。
レビューの独立性は5段階に分け、同じ段階を重ねても別の独立性とは数えなかった。
| 記号 | 種類 | 説明 |
|---|---|---|
| A | 自己レビュー | 開発した同じエージェントが見直す |
| B | 同一セッション内サブエージェント | 文脈を共有している |
| C | 別セッション | 開発側の報告書とコードだけを見る。ただし同じモデル |
| D | 別モデル | 今回はCodex |
| E | 人間 | 記録を確認できなかった |
別セッション(C)でも、モデルが同じなので盲点を共有している可能性がある。以降の数字は、特に断らない限り「エージェントの報告によると」の値である。ここでいう報告書は、各セッションが実験の終了時に書いた報告で、非公開のリポジトリに保存してあり、本記事では公開していない。私はそれらを読み取って整理しただけで、テストの再実行はしていない。
実験番号の末尾の英字(8A・8B、9C、11B、31Bなど)は、同じ系列の別セッションを表す。実装の別案のこともあれば、レビューや評価を担当したセッションのこともある。
事例
事例1:159件PASSのCLIに13件の欠陥(Exp1〜4)
開発したもの:WordPress・公開サイトを診断するCLI。仕様書から約17分で実装した。合格したテスト:159件が全てPASS、型検査もエラーなし。
別セッションに監査だけを依頼すると、13件(HIGH 2件、MEDIUM 7件、LOW 4件)が見つかった。
- robots.txt がUTF-8のBOM付きだと、先頭のルールが解釈されず、
Disallow: /を無視してページを取得してしまう(HIGH)。 - レスポンスヘッダを1バイトずつ遅く送り続けると、全体の時間制限が効かない(HIGH)。
どちらも、ループバックのサーバーと偽のDNS解決だけで再現できた。見逃した理由について、報告書は原因分析を書いていない。
その後:別のセッションが13件を修正した。修正前に回帰テスト48件を書いて29件の失敗を確認してから直しており、手順としては堅実である。それでもさらに別のセッションの再監査が、新たに5件(MEDIUM 1、LOW 4)を見つけた。報告書は、そのうちN-01(Shift_JISラベルのページでcp932拡張文字が無警告で文字化けする)を既存の欠陥として説明しており、修正で壊れたと記したものは確認できなかった。Python 3.12.3だけで失敗するテストも1件あった。最終修正後は、3つのPythonバージョンで270件が通っている。
事例2:ほぼ同評価の2実装に、異なる欠陥が見つかった(Exp8〜9C)
開発したもの:公開前リポジトリの検査CLI。同じ仕様から、2つのセッション(AとB)が独立に実装した(自作テストはAが87件、Bが132件)。合格したテスト:別セッションが用意した隠しテスト48件を、AもBも通過。100点満点の評価は、B 92.75点、A 93.67点。差は0.92点で、評価側は優劣を断定できないと結論した。
隠しテストは、2つの実装を区別できなかった。評価側が基準の枠外で追加テストを行うと、逆向きの欠陥が出た。
- B:出力先にハードリンクを指定すると、検査対象のREADMEを上書きする。
- A:病的なREADMEの1行で、処理時間が立方的に増える。
その後:実装側がそれぞれ修正した。再評価の結果は次のとおり。
| 実装 | 元の欠陥 | 修正後に見つかったこと |
|---|---|---|
| A | 解消 | [ を大量に並べて ] で閉じる入力で二乗時間化(1MiBで0.13秒→26.8秒)。Unicode空白の扱い変更で、抽出関数の比較161,199ケース中419件に差分 |
| B | 通常ケースは防止 | --json /dev/null が成功から終了コード2に変化。mode 000 のサブディレクトリ内ファイルへのハードリンクは防げない(修正前から未解決) |
なぜ修正の副作用を直前の評価が拾えなかったのか。再評価の報告書は、前回の再現条件が狭かったためだと書いている。前回の入力は「1行の [a]( の繰り返し」系だけで、修正の副作用を別の入力で出す設計になっていなかった。独立した評価も、条件次第で見逃す。
事例3:「病的入力は全て線形」への反例(Exp11〜11B)
開発したもの:事例2のAの堅牢化。報告されたこと:40種類の病的入力を100KB・1MBで測定し、最悪でも1MBで0.54秒、10倍の入力で約10倍(線形)。自作テスト152件が3つのPythonバージョンで通過し、旧実装との同値性も数十万ケースで確認。実時間300秒の予算も導入した。
別セッションの敵対的評価は、報告された改善自体は再現した。そのうえで、開発者が測定した入力リストの外で次を見つけた。
| 入力 | 結果 |
|---|---|
秘密候補1件+${ の繰り返し(80KB) | 11〜23秒 |
.gitignore に3万パターン×2千ファイル | 7秒 |
| 530KB×2万ファイル | 450秒でも終わらず、300秒の予算が効かない |
見逃しの原因は、評価側が「開発者の入力リスト外」と整理している。なお開発側の報告書も、時間予算がファイルの境界でしか確認されない(協調的)という残存リスクを書いてはいた。ただし見出しの「全て線形」「300秒予算」は、この限定なしでは読めない。
評価側は他にも、bind mountしたサブディレクトリ経由で対象内にファイルが作られる問題や、修正による互換性の変更(以前は成功した出力先を拒否する、既存出力ファイルの権限が0600から0644に広がるなど)を報告している。これらへの開発側の対処は、確認できた記録がない。
事例4:障害注入の強いテストでも、別の穴が見つかった(Exp31〜32B)
再開可能なファイル転送ツールで、障害注入ルール89件のうち実際に発火したのは131回、自作テストは137件、発火しないルールはテスト失敗にするガードまで入れていた。別セッションの敵対レビューも、破損を成功扱いにする経路は見つけられなかった。ここは自己評価が裏付けられた。
一方で、認証なしのアップロード作成の無制限化、低速な書き込み側(64KB/s未満)が切断されること、5xxの応答本文が途中で切れた場合の誤った終了コード(期待は3で、実際はトレースバックまたは2)など、MEDIUM 3件とLOW 4件が見つかった。修正側は、修正前に29件の失敗と26件のエラーになる新テスト34件を追加し、修正を1つずつ外した13個の変異を全て検出している。その修正を別セッションが再度レビューし、クォータを占有されるDoS(MEDIUM)を報告し、修正側の171件を全て通過してしまう変異が2つあることも示した。
事例5:別モデルのレビューと、両方が見逃した欠陥(Exp26・28)
Exp26では、実装PRが「PASS」だったにもかかわらず、評価側が、読み取り専用ファイルの保護の迂回、symlinkの置換、ハードリンクの切断という挙動変更を実測した。PR本文には書かれていなかった。
Exp28では、3つの修正対象にオーケストレーター(実験を起動する側のClaude)が既知欠陥9件を事前登録し、ClaudeとCodexに互いの結果を見せずにレビューさせた。
- 実験内の照合では、固有の指摘22件が真陽性と判定され、誤検出は両者0件だった。この照合は実験のオーケストレーター(Claude系統)が行ったもので、独立した人間による判定ではない。
- Codexだけが、ハードリンク経由で外部ファイルを破壊できる問題を確認済みで報告した。
- 既知欠陥9件のうち確認済みで検出できたのは、Claudeが4件、Codexが1件、合算で5件。読み取り専用ファイルの書き換え(K1-1)とハードリンクの切断(K1-3)は、両方が見逃した。
Claudeはクラウド上のLinux(root)、CodexはWindowsで動いており、環境差が結果に影響する。対象も3つ×1回なので、優劣は判断できない。
定量的な比較
比較の対象となった13組について、レビュー担当の独立性や指摘内容を分類し、その集計結果を以下に示す。
| 指標 | 件数 |
|---|---|
| 比較できた実験ペア | 13 |
| 新しい問題を発見したレビュー | 12 / 13 |
| 開発側の合格報告を確認できたペア | 12 |
| うち、合格報告のあとに問題が見つかった | 11 / 12 |
| 修正コードがレビュー対象だったペア | 9 |
最後の9件について、修正に起因する問題の性質を、重複しない4区分に分けた。「回帰」とひとまとめにしていない。
| 区分 | 組数(9組中) | 該当 |
|---|---|---|
| 厳密な回帰(以前は動いた入力・操作が壊れた) | 2 | 事例2のA・B、事例3の出力先の拒否 |
| 回帰以外の挙動・互換性の変更 | 3 | Exp26の保護迂回、Exp30のCookie結合、Exp32のリトライ挙動 |
| 潜在的な副作用・統合上の問題のみ | 2 | Exp27の統合時の衝突、事例4のRST切断時の誤分類(以前から誤っていた経路) |
| 新規指摘なし、または既存欠陥のみ | 2 | 事例1の再監査、Exp40 |
2+3+2+2で合計9組である。厳密な回帰(2組)と、回帰以外の挙動・互換性の変更(3組)を合わせると5/9になるが、5組で回帰が起きたという意味ではない。回帰は2組で、残る3組は仕様が曖昧な領域での挙動の変化である。
見つからなかった例もある。Exp40では、実OSSの不具合に対する別セッションの評価が、修正側に新たな欠陥を見つけなかった。ただし隠しテストは使えなかったので、「欠陥がない」とは言えない。
同一セッション内のサブエージェント(B)によるレビューは、6件のうち5件で何かを報告した。Exp35では、開発者が「保たれている」と書いた挙動が保たれていなかったことを見つけた。一方Exp36では、サブエージェントが20件を報告したが、後で別の経路から見つかった2件を見逃した。自己レビュー(A)が機能した記録もある。初期の修正案を既存テストが反証して差し替えた例(Exp38)、発火しない障害ルールのテストを自分で見つけた例(Exp31)などだ。
これらの数字は偏った母集団の観察件数であり、確率ではない。「13組」は、記録が残り、欠陥探しを目的に設計された別セッションのレビューに限った母集団である。
- レビューは欠陥を探す目的で設計されている。
- 私が実験を設計し、起動した。
- 報告書が残り、完了した実験だけが対象になっている。
- レビュー担当は全て同じモデルである(別モデルはExp28のみ)。
「12/13」を「ほぼ毎回見つかる」とは読まないでほしい。
見つかった問題の傾向と、テスト件数の意味
- テスト作者の想定外の入力:BOM、
/dev/null、ハードリンク、80KBの特定パターンなど。 - 非機能要件:処理時間、時間制限が効かない経路。
- 修正の副作用:直した箇所の隣で別の挙動が変わる。
- 主張の範囲:「全て線形」「健全な書き込み側は奪わない」など、測定した範囲より広く書かれた主張。
- PR本文に書かれない挙動変更。
テスト件数は、検出力の代わりにならなかった。159件(事例1)、152件(事例3)、171件(事例4)はどれも、後続のレビューで別の問題を見つけられている。事例4では、171件が全て通る変異が2つあった。問われるのは件数ではなく、そのテストが何を落とせるかである。修正前に失敗することを確認するテストは、複数の実験で実施されていた。ただし、その効果の定量比較はしていない。
実務で採用できるレビュー手順
実験で直接行ったことと、そこから私が導いた推奨を分ける。
| 手順 | 実験で直接確認したか | 備考 |
|---|---|---|
| 開発とレビューを別セッションにする | 確認した(13ペア) | 同一モデルである限界は残る |
| 隠しテストを別リポジトリに置き、ハッシュを先に固定する | 確認した(事例2) | 隠しテスト自体に判別力がない場合がある |
| 採点基準の枠外の敵対テストを加える | 確認した(事例2・3) | |
| 修正後に、修正とは別のセッションで再レビューする | 確認した(事例2・3・4) | |
| 変異テストで、テストが落とせるかを見る | 確認した(事例4) | |
| レビュー側に開発者の報告書を読ませる順序 | 部分的(31B・32Bは先に読んだと自己申告し独立性が下がった) | 推奨:まず仕様とコードだけで攻撃計画を作り、その後に報告書を渡す |
| 「全て」「完全」などの主張を測定ケースの列挙に置き換える | 未検証 | 推奨 |
| 重要な箇所に別モデルを加える | 確認した(Exp28、1実験のみ) | 既知欠陥の見逃しは残った |
| 人間が最終判断する条件 | 未検証(人間レビューの記録なし) | 推奨:権限・データ破壊・外部ファイルへの書き込みに関わる変更、PR本文に書かれていない挙動変更、レビュー間で見解が割れた箇所 |
障害再現テストは、レビュー側が「ループバックのサーバーと偽のDNSで再現する」といった形で作っていた。再現手順をレビューの成果物に含めさせると、開発側が修正の前後で同じ手順を再実行できる。
実験の限界
- 開発もレビューも主に同一モデルで、盲検は完全でない。
- 大半が1回の試行で、再現性や分散は評価していない。
- Exp26〜30は、起動・対象選定・照合を同じオーケストレーターが行っており、報告書はこれをClaude系統の制約として明記している。
- 対象は自作・合成の小さなコードが多く、実OSSは一部である。
- 人間によるレビューとの比較はできていない。
- 重大度の基準は報告書ごとに異なり、重大度では集計していない。
- 費用は確認できる数値がなく、コスト対効果は議論していない。
- 報告書の数字を再実行して確認していない。
結論
- テストPASSの報告は、仕様の範囲で動くことの証拠にはなるが、品質保証にはならなかった。
- 別セッションのレビューは13ペアのうち12ペアで新しい問題を見つけたが、偏った母集団であり、別セッションや別モデルでも見逃しは残った。
- 修正は、修正した本人の報告ではなく、別のセッションで再検証した。それが回帰や挙動変更を見つけた。
- 実務で優先したい候補は、修正後の別セッション再レビュー、基準外の敵対テスト、テストの検出力の確認である。
次回は、同じ実験群から「過去のOSSのバグを、答えを見ずに診断させたら上流の修正と一致するのか」を検証する。