「レビューを別のコンテキストに切り出して、『あなたはこれを書いていない懐疑的なレビュアーだ。欠陥があると仮定して探せ』と敵対的なフレーミングを与えると、自画自賛で終わらない指摘が返ってくる」。
r/ClaudeAI のそういう投稿が、upvote 860 を超えて支持されています(2026年8月時点)。
自分も似たような発想で、レビューを頼むときのプロンプトを少しずつ強めてきました。
厳しく言えばそのぶん見つかるはずだ、という感覚です。
ただ、これを確かめようとすると「指摘が何件返ってきたか」では足りません。
件数は水増しできてしまいます。
そこで、あらかじめ欠陥を 7 個仕込んだ Pull Request を用意して、検出率で測りました。 条件は 3 つです。
結論から書くと、一番厳しくした条件が、一番見逃しました。
正解リストを先に作る
題材には TypeScript + Hono + node:sqlite のタスク管理 API を使いました。
検証用に置いてあるサンドボックスのリポジトリです。
レビュー対象は、タグ付け・ページネーション・タスク共有を追加する機能追加 PR。
規模は 7 files changed, 421 insertions(+), 4 deletions(-) で、レビューが表面的になるほど大きくはなく、差が出ない程度に小さくもない、という狙いです。
この diff に、実務でありがちな欠陥を 7 個埋め込みました。
| # | 欠陥 | 重大度 |
|---|---|---|
| 1 | 認可チェック漏れ(他人のタスクが ID 指定で読める) | critical |
| 2 | Authorization ヘッダをエラーログに平文出力 | high |
| 3 | off-by-one(offset = page * limit) |
high |
| 4 | エラーの握り潰し(空 catch でタグ取得を握る) | medium |
| 5 |
limit の未クランプ(NaN・上限なし) |
medium |
| 6 | 競合状態(共有追加の check-then-act) | medium |
| 7 | N+1 クエリ | low |
仕込むときに一番気を使ったのが、テストで落ちないようにすることでした。
npm test は 12 件すべて green です。
CI が赤くなるバグはレビュー以前の話で、現実に問題になるのは「テストをすり抜けて残るバグ」のほうなので、そこに揃えています。
公平性のためにやったのは次の 3 つです。
- 実装とバグの仕込みは別のサブエージェントに委譲し、正解リストは 3 条件すべてのレビューが終わるまで自分でも開かなかった
- 3 条件とも入力は同じ diff だけで、正解リストはどの条件のコンテキストにも入れていない
- 報告基準(「実質的な問題だけ。スタイルや命名は報告しない」)と出力形式を全条件で揃えた
仕込んだ欠陥は「レビューで見つかりうる形」で作ったものです。
現実のバグ分布とは違います。
ここで出る検出率は、条件間の比較に使う数字として読んでください。
3条件の走らせ方
比べたのは次の 3 つです。
| 条件 | 実行するもの | 位置づけ |
|---|---|---|
| ① | Claude のサブエージェント(Opus 5) | 通常のコードレビュー |
| ② | /codex:review |
素の別 LLM レビュー |
| ③ | /codex:adversarial-review |
敵対フレーミング |
②と③は Claude Code の公式 Codex プラグインに入っているコマンドです。
自分で敵対プロンプトを書かなくても、素と敵対の両方が最初から用意されています。 これに気づいたのが今回の出発点でした。
今回は対話の外から回したかったので、プラグイン同梱のスクリプトを直接叩いています。
# 素のレビュー
node "$CLAUDE_PLUGIN_ROOT/scripts/codex-companion.mjs" review \
--base main --scope branch --wait
# 敵対レビュー
node "$CLAUDE_PLUGIN_ROOT/scripts/codex-companion.mjs" adversarial-review \
--base main --scope branch --wait
--base main --scope branch で「main との差分をレビューする」という指定になり、--wait でフォアグラウンド実行になります。
普段づかいなら /codex:review と /codex:adversarial-review をそのまま打てば同じことができます。
条件①は、②③と粒度を揃えるために敵対的な文言をひとつも入れない素朴な依頼にしました。
「この PR をレビューしてください」に、報告基準と出力形式の指定を足しただけです。
ここに「厳しく見て」と書いてしまうと、比べたいもの自体が混ざります。
条件別の検出結果
3 条件を同じ PR に当てた結果です。
| 条件 | 総指摘 | 仕込み7件の検出 | 仕込み外の実在指摘 | 所要時間 |
|---|---|---|---|---|
| ① 通常レビュー | 10 件 | 7/7 | 3 件 | 5分37秒 |
| ② 素の Codex | 8 件 | 6/7 | 2 件 | 5分13秒 |
| ③ 敵対 Codex | 4 件 | 3/7 | 1 件 | 2分31秒 |
敵対フレーミングを足した条件が、指摘数でも検出率でも最下位でした。
所要時間も半分以下です。
ただし、重大度で分けると見え方が変わります。
| 重大度 | 仕込み数 | ① 通常 | ② 素 | ③ 敵対 |
|---|---|---|---|---|
| critical | 1 | 1/1 | 1/1 | 1/1 |
| high | 2 | 2/2 | 2/2 | 2/2 |
| medium | 3 | 3/3 | 2/3 | 0/3 |
| low | 1 | 1/1 | 1/1 | 0/1 |
敵対レビューは critical と high を取りこぼしていません。落としたのは medium 以下だけです。 見つけられなかったというより、報告する基準を上げている、という挙動に見えます。
③が落とした 4 件は、握り潰し・limit の未クランプ・N+1・競合状態でした。
このうち limit の件は、独立した指摘としては挙がっていないものの、off-by-one の修正案の中で「page と limit を正の整数として検証してください」と触れられています。
見えてはいたが、独立した指摘として立てるほどではないと判断した、と読めます。
仕込んでいないバグの発見
想定外だったのが、3 条件とも私が仕込んでいない本物のバグを見つけてきたことです。
task_tags と task_shares の外部キーに ON DELETE CASCADE が無く、タグを付けたタスクや共有したタスクを削除すると外部キー制約で 500 になる、というものです。
既存の削除 API が新機能の追加によって壊れる、リグレッションにあたります。
実装を担当したサブエージェントが自然にやってしまったもので、正解リストにも載っていません。
②はこれに加えて「自分自身へのタスク共有が 201 で通る」を、①は「Number(true) === 1 なので {"user_id": true} が ID 1 への共有として成立する」「追加されたテストのアサーションが緩く、off-by-one を検出できない」を挙げました。
最後のものは、仕込んだ欠陥 #3 が CI をすり抜けた理由そのものです。
裏取りのやり方
指摘の中身だけでなく、その裏の取り方にも差が出ていました。
3 条件とも diff を読むだけでは済ませていません。
条件①は別ユーザーのトークンで API を叩き、他人のタスクが 200 で返るところまで実演してみせました。
?limit=abc で 500 になること、{"user_id": true} が通ること。
どちらも実行して確かめたうえで報告しています。
条件②が回したのは npm test と npm run typecheck、それに node --import tsx で組んだ検証用のデータベース。
条件③は node -e で DatabaseSync を起動し、外部キー制約が既定で有効かどうかを見ています。
面白かったのが③の途中経過です。
最初のパスでは approve(問題なし)と判定していたのに、調査を進めるうちに needs-attention へ覆しています。
1 回読んで終わりにせず、自分の初期判断を壊しにいっていました。
なぜ指摘が減ったのか
これだけ手を動かしていて、それでも報告が 4 件だった理由は、プロンプトを読むと分かります。
敵対レビューのプロンプトは、プラグインの中に Markdown ファイルとして置いてあります。
<calibration_rules>
Prefer one strong finding over several weak ones.
Do not dilute serious issues with filler.
If the change looks safe, say so directly and return no findings.
</calibration_rules>
「弱い指摘をいくつも並べるより、強い指摘を 1 つ」「深刻な問題を埋め草で薄めるな」。
報告数を絞れという指示が、はっきり書かれています。
同じファイルには攻撃対象の優先順位も指定されています。
<attack_surface>
Prioritize the kinds of failures that are expensive, dangerous, or hard to detect:
- auth, permissions, tenant isolation, and trust boundaries
- data loss, corruption, duplication, and irreversible state changes
- rollback safety, retries, partial failure, and idempotency gaps
- race conditions, ordering assumptions, stale state, and re-entrancy
...
</attack_surface>
認可・データ破壊・競合状態といった、高コストで見つけにくい失敗にリソースを寄せる設計です。
今回 critical と high が満点だったのは、この優先順位がそのまま効いた結果だと考えています。
つまり敵対フレーミングがやっているのは、探す範囲を広げることではなく、報告する基準を上げることでした。
厳しくすればもっと出てくる、という私の元の理解は逆方向だったわけです。
ひとつ説明が付かないのが、競合状態を③が落としたことです。
race conditions は攻撃面に名指しで挙がっているのに、共有追加の check-then-act(存在を確認してから挿入するまでの間に、別のリクエストが割り込める作り)は報告されませんでした。
3 条件の中で拾ったのは①だけで、しかも severity は low の判定でした。
攻撃面に挙げることと実際に見つけることは別、ということかもしれません。
この検証の限界
数字がきれいに出たぶん、条件を書いておきます。
①と②③でモデルが違います。
①は Opus 5、②③は Codex CLI(gpt-5 系)です。
①が最多だったことを「Claude のほうが優秀」とは読めません。
読み取れるのは、②と③という同じモデル・同じ入力でフレーミングだけを変えた対の差だけです。
②は私のグローバル設定に助けられています。 ~/.codex/AGENTS.md に、N+1 やオーバーフェッチを避けること・外部入力をすべて検証すること・try-catch で握りつぶさず根本原因を修正することを方針として書いてあり、②の出力にはこれを根拠に挙げた指摘が 3 件ありました。
③は独自プロンプトで上書きされるため参照していません。
②の 6/7 という数字には、この個人設定の寄与が含まれています。
n=1 です。
1 つの PR に 1 回ずつ走らせただけで、LLM は非決定的なので再実行すれば結果は動きます。
傾向として読むべき数字です。
元ネタと完全に同じものも試していません。
元の投稿は敵対フレーミングを自分でプロンプトに書く話で、今回③に使ったのは公式プラグインの敵対モードです。
方向性は同じでも、文面は別物です。
まとめ
敵対レビューは、報告の基準を重大度で引き上げる道具でした。
網羅的に洗い出したいときは素のレビュー、出荷してよいかを判断したいときは敵対レビュー。
片方が 2 分半、もう片方が 5 分ほどなので、順番に両方回しても大きな負担にはなりません。
仕込んだ欠陥の正解リストと、3 条件それぞれの生ログは公開しています。
Xをフォローいただけると嬉しいです!
AI駆動開発(特に Claude Code)のノウハウや Tips をよく発信しています!
