17のAIレビューが見逃した。14の修正は全部CI成功、それでもmergeできたのは1つだった
実PostgreSQLテスト基盤を14構成で実装し、統合版を17構成で独立レビューした。17件のraw reviewはいずれもblocking Majorを検出できず、その後の修正14件はCIが全成功。それでも最終裁定でmerge-readyだったのは1件だけだった。
要点(TL;DR)
出典ラベル付き。項目をクリックすると該当箇所へ移動します(折りたたみ内なら自動で開きます)。
- 最終結果 Major修正14候補は全件CI SUCCESS。それでもmerge-readyは1 / 14だった。
- レビュー 17件のraw independent reviewは、後から確定したblocking root causeを検出していなかった。
- 核心 resource IDだけに依存せず、create前のownership labelでpartial-create failureを回収した案だけがmerge-readyになった。
- 注意 17-model reviewのGoldは完全blindな事前locked Goldではなく、追加一次調査を含むpost-hoc adjudication。
検討に至った背景
#はじめに
AIが書いたコードを、別のAIにレビューさせれば安全になるのか。
FND-03では、この問いをかなり厳しい形で試すことになりました。
対象は minimal-bank-system のIssue #41、実PostgreSQL 18を使うintegration test基盤です。TestcontainersでPostgreSQLを起動し、test isolation、parallel execution、cleanup、startup failure、CIまで含めて、後続Issueが安全に使える共通fixtureを作ります。
最初に14の Model + Agent/Harness へ同じIssueを独立実装させました。比較後、良い設計を統合したFinal Synthesisを作り、当時は98/100相当まで仕上がったと評価していました。
そのFinal Synthesisを17のAI構成へ独立レビューさせます。
結果は、completedしたReviewerがすべてAPPROVEでした。
ところが、その後Testcontainers 4.13.0の一次sourceまで追うと、container cleanupのfailure pathにmerge-blocking Majorが残っていました。
そこで今度は、そのMajorを14構成へ同一条件で修正させました。14件すべてexact Head CIはSUCCESSです。
それでも3つのJudgeで一次証拠を突き合わせると、merge-readyと判定できたのは1件だけでした。
この記事では、細かな候補別採点表を並べるのではなく、なぜCI greenとAIレビューを重ねても不足したのか、そして複数AIを使うとき最後に何を信用すべきかを整理します。
全候補、全順位、PR、Head SHA、CI、Judge内訳は Benchmarks に分けて掲載します。
調査プロンプト
プロンプト全文を表示する
minimal-bank-system Issue #41 FND-03「実PostgreSQL integration test基盤を確立する」の完全実験アーカイブを一次資料として確認し、公開記事を作成する。
記事では、初期実装14候補、Final Synthesis、17-model independent review、post-hoc Goldで発見されたTestcontainers cleanup Major、14-model Major-fix benchmark、3-Judge adjudication、最終production outcomeまでを一つの流れとして扱う。
ただし、記事本文へ中間Stageの詳細ランキングや全候補表を詰め込まない。記事は最終結果と実務上の学びを中心にし、候補別スコア、処理時間、PR、CI、Judge内訳など詳細なBenchmark結果はBenchmarksページへ分離する。
重要な方法論上の注意として、review benchmarkのGoldは完全blindな事前locked Goldではなく post-hoc adjudication であることを明記する。CI greenをfailure-path correctnessの証明として扱わない。
AIエージェントの回答
FND-03 完全実験アーカイブ
- Web検索
- なし
- 添付
- なし
- 完全性
- edited
minimal-bank-system Issue #41のcanonical archive、candidate exact Head、PR、CI、post-hoc Gold、3-Judge adjudication、最終production outcomeを一次資料として公開用に整理した。
#FND-03 完全実験アーカイブ
#対象
Issue #41 [FND-03] 実PostgreSQL integration test基盤を確立する。
PostgreSQL 18 / Testcontainersを使い、test isolation、database lifecycle、parallel execution、cleanup、failure reporting、CI実行までを共通integration test foundationとして成立させる課題です。
#実験の流れ
FND-03は次の順序で実施しました。
- 14構成による初期実装Benchmark
- 上位候補の知見を統合したFinal Synthesis
- Final Synthesisへの17-model independent review
- Testcontainers 4.13.0一次source突合によるpost-hoc Gold / cleanup Major確定
- 同一Majorを14構成へ独立修正させるBenchmark
- 3 JudgeによるFinding adjudication
- 選択したarchitectureとtest資産を統合したproduction implementation
#Canonicalな最終結果
- Review Benchmarkのblocking Gold root cause検出: 0 / 17
- Major-fix候補のexact Head CI: 14 / 14 SUCCESS
- 3-Judge裁定のmerge-ready: 1 / 14
- Final fix Benchmark 1位: GPT-5.6 Sol / Codex — 94 / 100
- Final production: Agent B APPROVE、Blocker / Major / Minor / Nit = 0 / 0 / 0 / 0
- PR #104 MERGED、Issue #41 CLOSED / COMPLETED
候補別のランキング、全スコア、処理時間、PR、Head SHA、CI、Judge別結果は記事本文へ重複掲載せず、amane AI LabのBenchmarksページに掲載します。
#技術的な核心
Testcontainers 4.13.0では、Docker resourceの削除完了前にdisposed stateがlatchされるfailure pathがあり、同じfailed instanceへの2回目のDisposeがno-opになり得ます。
さらにMajor修正比較では、Docker create成功後・Testcontainers内部へcontainer IDが保存される前に失敗するpartial-create pathが重要な裁定点になりました。
最終的に選ばれたarchitectureは、create前にunique ownership labelを確立し、Testcontainers instanceやIDだけに依存せず、label query / remove / re-queryでresource absenceを確認します。
#方法論上の注意
17-model review benchmarkのGoldは完全blindな事前locked Goldではありません。
raw review収集後の追加一次source突合でblocking Majorが明確化されたため、canonical archiveでは post_hoc_adjudication: true としています。
そのため「17モデルが事前に固定されたblind Gold testで全滅した」とは表現しません。
#一次資料
AI回答の合成
#17のAIレビューが見逃した。14の修正は全部CI成功、それでもmergeできたのは1つだった
#まず結果
FND-03で最終的に残った数字は、かなり極端でした。
- 初期実装: 14構成、13件を採点
- Final Synthesis: 当時98/100相当と評価
- 独立レビュー: 17構成
- blocking Majorを実質検出したReviewer: 0 / 17
- Major修正: 14構成
- exact Head CI: 14 / 14 SUCCESS
- 3-Judge最終裁定でmerge-ready: 1 / 14
- 最終production: Agent B APPROVE、Blocker / Major / Minor / Nit = 0 / 0 / 0 / 0
最終修正Benchmarkの1位は GPT-5.6 Sol / Codex — 94/100 でした。2位はClaude Opus 5 / Claude Code 80、3位はGPT-5.6 Luna / Codex 77です。
これは、このIssue、このHarness、このEffort、この1回の実行結果です。モデル一般の性能順位ではありません。
全14候補の順位、中間Stageのランキング、PR、Head SHA、CI、Judge内訳は Benchmarks に分けています。
#FND-03で作ったもの
FND-03の目的は、実PostgreSQL 18を使うintegration test基盤です。
単にcontainerを起動してqueryが通れば完了、というIssueではありません。
後続Issueではrow lock、advisory lock、constraint、trigger、migrationなどPostgreSQL固有の挙動を検証します。その土台になるため、test基盤側で次を保証する必要があります。
- test同士が状態を共有しない
- database lifecycleを自動管理する
- 並列実行しても壊れない
- cleanup失敗を握り潰さない
- startup / connection failureを明確なtest failureにする
- CIでも同じ実PostgreSQL testを動かす
- InMemory / SQLiteへfallbackしない
初期実装比較では、database isolation、parallel safety、cleanup failure handlingで差が付きました。
#14候補を比較し、Final Synthesisを作った
初期BenchmarkではGPT-5.6 Sol / Codexが96/100で1位でした。
ただし、最終成果物は1位候補をそのまま採用していません。複数候補の良い設計とfailure testを選んでFinal Synthesisを作りました。
当時の評価は98/100。CIもSUCCESS。cleanup retry、startup failure、connection failure、parallel executionまでかなり厚く検証していました。
ここだけを見ると、十分にmergeできそうです。
実際、この評価は後から SUPERSEDED になりました。
#17の独立レビューは、blocking Majorを見つけられなかった
Final Synthesisへ、同じ条件で17のModel + Harnessを独立レビューとして投入しました。
completedしたReviewerはすべてAPPROVE。レビュー品質には差がありましたが、後からtechnical Goldになったblocking root causeを実質検出したReviewerは0件でした。
問題はTestcontainers .NET 4.13.0のdispose semanticsにありました。
概略すると、cleanup時にDocker resourceの削除が失敗しても、Testcontainers内部では先にdisposed stateが固定される経路があります。そのfailed instanceへもう一度 DisposeAsync() しても、2回目はno-op成功になり得ます。
Final Synthesisは「同じinstanceでcleanupをretryできる」ことを自分のtestで確認していました。しかし、そのtestが再現していたfailureと、Testcontainers内部の本当のfailure pathは同じではありませんでした。
つまり、テストはあった。failure injectionもあった。それでも、依存libraryのstate machineを誤解していた。
#Green CIは、この問題を反証できない
このMajorが厄介なのは、通常のCIではほぼ見えない点です。
正常系ではcontainerは消えます。databaseも消えます。testも全部通ります。
必要なのは、Docker resource削除の途中だけが失敗し、その後同じTestcontainers instanceをretryするような狭いfailure pathです。
そのためFND-03では、次の区別がはっきりしました。
CI SUCCESSは「実行したtestが通った」証拠です。実行していないfailure pathまで正しい証拠ではありません。
これは当たり前に見えますが、14候補すべてCI SUCCESSだった最終修正ラウンドでも、同じことがもう一度起きました。
#Majorを教えてから14構成に直させた
次の実験では、見つかったcleanup Majorを明示し、14のModel + Harnessへ同一Baseから独立修正させました。
今度は問題を知っています。
しかも14件すべてexact Head CI SUCCESSでした。
それでも3 Judgeで実コードとTestcontainers 4.13.0の一次sourceを突き合わせると、merge-readyは1件だけでした。
理由は、元のMajorを直そうとしたことで、さらに深いownership問題が見えたためです。
#IDを取得する前に失敗したら、誰がcontainerを回収するのか
最終裁定で特に効いたのがpartial-create pathです。
Docker側ではcontainer creationに成功した。しかしTestcontainersがそのIDを自分の内部stateへ保存する前に失敗した。
この瞬間には、
- Docker上にはcontainerが存在する
- application側からcandidate IDを取得できない
- Testcontainersのnative Disposeを信用できない
という状態が起こり得ます。
多くの修正案は、container IDやTestcontainers instanceをcleanup ownershipの根拠にしていました。そのためIDを失うと、実resourceが残っていてもauthoritativeなownerを失います。
1位のGPT-5.6 Sol / Codex案は、containerをcreateする前にunique ownership labelを決めました。
そしてnative Disposeの成否だけに依存せず、labelでDocker resourceを検索し、removeし、もう一度検索して消えたことを確認します。
重要なのは「retryを増やした」ことではありません。
壊れ得るlibrary objectとは別に、resource ownershipを識別できる手段を持ったことです。
#多数決ではなく、一次証拠でJudgeを裁定した
Major修正Benchmarkでは3つのJudgeを使いました。
Judge同士の評価も一致していません。
あるJudgeが高く評価した候補を、別Judgeはmerge不可としました。そこでraw scoreの平均を最終結果にはしていません。
Testcontainers 4.13.0のsource、candidate exact Head、failure test、CIを戻って確認し、Findingごとに成立するかを裁定してから最終順位を決めました。
これはAIを増やすときに重要だと思っています。
AI Reviewerを3つ使うことと、3票で多数決することは同じではありません。
異なるReviewerから異なる仮説を集め、最後は一次証拠へ戻る。
FND-03ではこの方法が必要でした。
#Review Benchmarkには方法論上の注意がある
17-model review benchmarkについては、結果の扱いに注意が必要です。
最終的なGold Majorは、全Reviewerの回答を集める前に完全固定されていたものではありません。
最初のReferenceを固定した後、追加でTestcontainers 4.13.0の一次sourceを調べてMajorを明確化しました。そのためarchiveでは post_hoc_adjudication: true としています。
つまり、「17モデルにblind testをして全員不合格だった」という表現はしません。
正確には、17件のraw reviewを保存した後、追加一次調査で確定したblocking Goldに照らすと、17件ともそのroot causeを検出していなかった、です。
失敗結果を強く見せるために実験条件を単純化しないことも、このLabでは重要だと考えています。
#実装能力、レビュー能力、修正能力は分けて見た方がいい
FND-03全体を見ると、少なくとも3つの能力を分けた方がよさそうです。
- 未知のIssueを実装する能力
- 一見完成した実装から未知の欠陥を見つける能力
- root causeを提示された後にfailure spaceを閉じる能力
同じモデルが全部で同じ順位になるとは限りません。
初期実装で高得点でもReviewerとしてMajorを見つけられるとは限らず、Majorを理解しても完全なfixを作れるとは限りません。
「コーディング性能」という1つの数字へまとめると、この差は見えなくなります。
#実務なら14候補も17Reviewerも使わない
この規模はBenchmarkだから行っています。
普段の開発なら、ここまで増やす必要はありません。
私なら重要な基盤Issueでは、
- 1〜3の独立実装
- 別Model / Harnessによる独立レビュー
- failure pathを意識したtest
- 依存libraryの挙動が重要なら一次source確認
- 最後はIssueと実コードへ戻ってmerge判断
くらいに縮めます。
特に、cleanup、transaction、lock、retry、cancellationのような失敗時のstate transitionが品質を決めるコードでは、正常系CIの緑だけで判断しない方がいいです。
#今回の最終production
最終production implementationでは、1位PR #108のownership architectureを軸に、別候補のactual Testcontainers latch / second-no-op testと、元Baseにあったunreachable-Docker regressionを統合しました。
Final Headに対するAgent B独立レビューはAPPROVE、Blocker / Major / Minor / Nitはすべて0。pre-merge、post-merge CIもSUCCESSし、Issue #41をcloseしています。
ここでも「Benchmark 1位をそのままproductionへ入れた」わけではありません。
候補比較は意思決定材料であって、production outcomeは別の成果物として残しています。
#最後に残った教訓
FND-03で一番印象に残ったのは、AIが間違えたことではありません。
かなり強い実装、かなり厚いtest、複数のAI Reviewer、green CIが揃っていても、全員が同じ前提を見誤ることがあるという点です。
AIを増やすほど安全になるとは限りません。同じ証拠、同じ抽象化、同じlibrary理解に依存していれば、見逃しも共有します。
だから最後に必要なのは、多数決よりも、
- 何を証明したtestなのか
- failure時に誰がresourceを所有するのか
- 依存libraryは本当にそのsemanticsなのか
- CI greenが何を証明していないのか
を一次証拠へ戻って確認する工程でした。
FND-03は、モデル比較というより、AIエージェントを使った開発で「何を信用するか」を試した実験になりました。
AI回答の合成(わかりやすい説明)
#わかりやすく言うと
FND-03では、AIにPostgreSQLのテスト基盤を作らせ、それを別のAIにレビューさせ、さらに見つかった問題をまた複数のAIに直させました。
一番重要だった結果は、次の3つです。
- 17のAIレビューでは、後から確定した重大なcleanup問題を誰も見つけられなかった
- 問題を教えた後の14修正案は、全部CIが成功した
- それでも最終的にそのままmergeできると判断できた修正は1件だけだった
なぜこんなことが起きたかというと、通常のテストでは起きにくい失敗経路に問題があったからです。
PostgreSQL containerを削除するとき、途中で失敗してもTestcontainers側では「もうdispose済み」と扱われる場合があります。その状態で同じobjectをもう一度cleanupしても、本当のcontainerが残っているのに処理が何もしない可能性がありました。
さらに修正案を比べると、「containerは作られたが、そのIDを取得する前に失敗した場合はどうするか」という別の問題も見つかりました。
最終的に一番強かった案は、containerのIDだけに頼らず、作る前から専用labelでownershipを持つ方式でした。IDを失っても、そのlabelを使えば残ったcontainerを探して削除できます。
この実験から、CIが成功していることは重要ですが、それだけで失敗時の挙動まで正しいとは言えないことが分かりました。
AIレビューも同じです。Reviewerを増やすだけでは、全員が同じ前提を間違えている場合に見逃しを防げません。
重要なコードでは、複数AIの意見を多数決にするより、実コード、テスト、依存libraryの一次sourceへ戻って確認する方が安全です。
詳細なランキングや全候補の結果はBenchmarksページに分けています。
合成結果の要約
#まとめ
FND-03では、実PostgreSQL integration test基盤を複数AIへ独立実装させ、統合版へ独立レビューを掛け、そこで残ったMajorをさらに複数AIへ独立修正させました。
最終的に強く残ったのは、モデル順位よりも次の事実です。
- 17件のraw independent reviewは、後からtechnical Goldとして確定したblocking root causeを検出していなかった
- Major修正14候補はすべてexact Head CI SUCCESSだった
- 3-Judge裁定でmerge-readyだったのは1 / 14だった
- 最終productionはBenchmark 1位をそのまま採用せず、複数候補のtest資産と既存regressionを統合して再レビューした
FND-03の問題は、通常の正常系では見えにくいresource cleanupとownershipのfailure pathにありました。
この種のコードでは、「testがある」「CIが緑」「複数ReviewerがApprove」という証拠を積み重ねても、それらが同じ前提に依存していると同じ見逃しを共有します。
特に重要だったのは、壊れ得るTestcontainers instanceそのものをresource ownershipの唯一の根拠にしないことでした。container create前に独立したownership labelを持つことで、ID取得前のfailureでも実resourceを再発見し、cleanupできる設計になりました。
また、17-model review benchmarkのGoldは完全blindな事前locked Goldではなく、raw review収集後の追加一次source調査で明確化したpost-hoc adjudicationです。この制約も結果と一緒に残します。
AIを実務で使うとき、候補やReviewerを増やすこと自体は品質保証になりません。
最終的に必要なのは、どのtestが何を証明しているか、failure時のownerは誰か、依存libraryのsemanticsは本当に正しいかを一次証拠で確認することです。
FND-03は、AIの実装性能だけでなく、レビュー性能、既知問題の修正性能、そして複数AIの結果をどう裁定するかまで含めた実験になりました。
#FND-04ではどう変えるか
次のFND-04では、この反省を実験設計へ反映します。
まず、candidateやReviewerを見る前にReferenceを強くします。FND-04はEF Core / Npgsql migrationの挙動に依存するため、外部libraryの前提、failure path、testが証明する範囲、evaluator専用runtime probeを事前に固定します。特にmodel drift、explicit migrator failure、API startupでschemaが変化しないことは、visible testが存在するだけでなく、意図的に壊したとき本当にFAILすることまで確認します。
candidate数も減らします。FND-03のように14構成を毎回投入するのではなく、実績の安定したCore候補と少数のChallengerへ絞り、8構成程度を基本とします。一方で、同じモデルを異なるHarnessで継続観測する枠は残します。評価単位は引き続き Model + Agent/Harness + Effort + execution attempt です。
セルフレビューは、実装execution内の曖昧な確認ではなく、H0実装 → fresh contextでFormal Self-Review → H1修正として分離します。これにより、H0 Score、H1 Score、Self-Review Gain、valid finding、false positive、追加時間、追加差分を測定できるようにします。セルフレビューはAgent Bの代替にはしません。
外部レビューも人数より役割の異質性を優先します。同一プロンプトを多数へ投げる方式から、Specification / Scope、Framework semantics、Runtime / Failure injection、Test assurance、Deep technical、Fast independent reviewのように責務を分けた少数Reviewerへ寄せます。
さらに、review性能の測定にはReal Final Synthesisだけでなく、事前に欠陥を埋め込んだControlled Mutantを併用する予定です。Real targetは実際のmerge gate、Mutant targetはpre-locked Goldによるreview能力測定と役割を分けます。これにより、FND-03で発生したpost-hoc Goldの弱点を減らします。
Judgeも常時3件ではなく、まず2 Judgeで比較し、VerdictやMajor root causeが割れた場合だけ3件目を追加する方式を基本にします。Major修正が必要になった場合も、全モデル一斉修正ではなく上位候補を中心に絞ります。
最後に、production mergeとbenchmark archiveを分離します。candidate registry、Head、CI、raw artifact、tag、PR close、branch cleanupは継続して残しますが、archive整理そのものを次Issue開始のcritical pathにはしません。
FND-04では、単一の総合順位だけでなく、Implementation Score / Self-Review Gain / External Review Quality / Gold Alignment / Execution Reliability / Time・Costを別々に蓄積する方向へ進めます。
詳細な候補別順位、スコア、処理時間、PR、CI、Judge結果は Benchmarks にまとめています。
合成結果の要約(わかりやすい説明)
#まとめ
今回の実験では、AIにコードを書かせるだけでなく、別のAIにレビューさせ、見つかった問題をさらに複数のAIに直させました。
それでも、AIを増やせば自動的に安全になるわけではありませんでした。
17のレビュー結果は、後から分かった重大なcleanup問題を見つけられていませんでした。問題を教えた後の14修正案は全部CIが成功しましたが、そのままmergeできると判断できたのは1件だけです。
理由は、通常のテストでは起きにくい失敗経路にありました。
containerを作った直後や削除の途中で失敗すると、管理objectが壊れたりIDを失ったりして、実際のcontainerだけが残る可能性があります。
最も強かった修正は、管理objectやcontainer IDだけに頼らず、作成前から専用labelでownershipを持つ方法でした。
この結果から、重要なコードでは次を確認した方がよいと考えています。
- CIが何を確認していて、何を確認していないか
- 失敗したときresourceを誰が回収するか
- 依存libraryの実際の挙動を確認したか
- 複数AIの意見が同じ前提に依存していないか
AIの多数決より、最後に一次証拠へ戻ることが重要でした。
#FND-04で変えること
次のFND-04では、FND-03と同じやり方をそのまま繰り返しません。
- 実装候補は14から8程度へ減らす
- EF Core / Npgsqlの前提やfailure pathをcandidate実行前に確認する
- evaluator専用のadversarial probeを事前に用意する
- セルフレビューをH0実装、Formal Self-Review、H1修正に分けて効果を測る
- Reviewer数を減らし、仕様、framework、failure path、test assuranceなど役割を分ける
- Real Final Synthesisとは別に、意図的な欠陥を入れたControlled Mutantでreview能力を測る
- Judgeはまず2件とし、重大な不一致がある場合だけ3件目を使う
- Major修正は原則として上位候補だけに絞る
- production mergeとbenchmark archiveを別工程として扱う
また、実装点数だけでなく、Self-Review Gain、External Review Quality、Gold Alignment、Execution Reliability、Time / Costを分けて記録する予定です。
全候補の詳しい順位やスコアはBenchmarksページで公開します。
Edit on GitHub
コメント
コメントは GitHub Discussions(giscus)上の第三者投稿です。読み込みと投稿にはGitHubへの接続が必要です。