TL;DR
- OpenAI公式の
codex-plugin-ccを使うと、Claude Code のセッション内から Codex にコードレビューを依頼できる。 - Claude Code には
/code-review、Codex 側には/codex:reviewと/codex:adversarial-reviewがあり、レビューの聞き方を変えられる。 - 今回は、わざと15個のバグを仕込んだURL短縮サービスを用意し、4つのレビュー方式を各2回ずつ試した。
- 結果として、検出数の差は「ClaudeかCodexか」よりも、観点を明示して広く聞くか、専用コマンドでスコープを絞って聞くかの影響が大きそうだった。
- ただし
n=2の小さな検証であり、モデル性能の優劣を結論づけるものではない。
検出数(2回中1回以上で検出 / 15個):
| 条件 | 方式 | 検出数 |
|---|---|---|
| Claude自由記述 | 観点を明示した自由記述プロンプト | 14 |
Claude /code-review
|
専用コマンド | 12 |
Codex /codex:review
|
専用コマンド | 11 |
Codex /codex:adversarial-review
|
専用コマンド(敵対的レビュー) | 6 |
なお、/codex:adversarial-review は通常のバグ網羅レビューではなく、実装方針・設計判断・信頼境界に踏み込むレビューなので、単純な検出数だけでは評価しにくい。
環境: Claude Opus / effort high、Codex gpt-5.5 / effort high。
codex-plugin-cc とは
codex-plugin-cc は OpenAI公式 のプラグインで、Claude Code に Codex を差し込み、Claude Code のセッションから離れずに Codex へコードを渡してレビュー・分析させられる。
発想の核は、別プロバイダの別モデルにレビューさせることで、sycophancy、いわゆる「おべっか」バイアスに引きずられにくい第三者レビューに近づける、という点にある。
今回はこれを口実に、「Claude と Codex のレビューは何がどう違うのか」を実測してみた。
セットアップ
Claude Code 内で順に実行する。
/plugin marketplace add openai/codex-plugin-cc
/plugin install codex@openai-codex
/reload-plugins
/codex:setup
/codex:setup がローカルの Codex CLI を検出し、認証と設定(~/.codex/config.toml)をひも付ける。別プログラムを新規に入れるわけではなく、マシン上の既存の Codex を同じ認証情報で呼ぶ。
主なコマンド:
| コマンド | 役割 |
|---|---|
/codex:review |
通常のレビュー |
/codex:adversarial-review |
実装方針・設計判断に挑む攻撃的レビュー |
/codex:status / /codex:result / /codex:cancel
|
バックグラウンドジョブ管理 |
/codex:transfer |
セッションを Codex スレッドへ引き継ぐ |
検証方法
題材コード
今回は「URL短縮サービス」を題材にし、 種類の異なるバグを15個 仕込んだ。正確性・並行性・セキュリティ・プライバシー・設計の各カテゴリにまたがるよう設計している。
// shortener.ts
// URL短縮サービス。POST /shorten で短縮、GET /:code で元URLへリダイレクト。
import express from "express";
import { Pool } from "pg";
const app = express();
app.use(express.json());
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const ADMIN_TOKEN = "YOUR_ADMIN_TOKEN"; // 管理APIトークン(例)
const ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const cache = new Map<string, string>();
function encode(id: number): string {
let s = "";
while (id > 0) {
s = ALPHABET[id % ALPHABET.length] + s;
id = id / ALPHABET.length;
}
return s;
}
app.post("/shorten", async (req, res) => {
try {
const { url } = req.body;
console.log(`shorten ip=${req.socket.remoteAddress} url=${url}`);
const row = await pool.query(`SELECT MAX(id) as max FROM links`);
const nextId = Number(row.rows[0].max) + 1;
const code = encode(nextId);
pool.query(
`INSERT INTO links (id, code, url, hits) VALUES (${nextId}, '${code}', '${url}', 0)`
);
cache.set(code, url);
return res.json({ ok: true, code });
} catch (e) {
res.json({ ok: true, code: "xxxxxx" });
}
});
app.get("/:code", async (req, res) => {
const code = req.params.code;
let url = cache.get(code);
if (!url) {
const row = await pool.query(`SELECT url FROM links WHERE code = '${code}'`);
url = row.rows[0].url;
cache.set(code, url);
}
const hits = await pool.query(`SELECT hits FROM links WHERE code = '${code}'`);
pool.query(`UPDATE links SET hits = ${Number(hits.rows[0].hits) + 1} WHERE code = '${code}'`);
return res.redirect(url);
});
app.listen(3000);
仕込んだバグ(抜粋):
- 正確性/並行処理:
encodeのMath.floor漏れ、INSERTの await 漏れ、SELECT MAX(id)+1の採番レース、hits の lost update、存在しない code でのrows[0]クラッシュ、Number の 2^53 精度 - セキュリティ: SQLインジェクション、トークン直書き、オープンリダイレクト
- プライバシー: IP+URL のログ出力
- 設計/ドメイン: 連番コードの列挙可能性+レート制限なし、レイヤリング欠如、catch がエラーを握り潰して成功を偽装
仕込んだバグ15個
| ID | 箇所 | 内容 | カテゴリ |
|---|---|---|---|
| S1 | encode() |
id = id / ALPHABET.length に Math.floor がなく非整数化。コードが壊れる |
正確性 |
| S2 | 各クエリ |
url / code をテンプレート文字列に直挿し → SQLインジェクション |
セキュリティ |
| S3 | ADMIN_TOKEN |
シークレットをソース直書き(かつ未使用) | セキュリティ |
| S4 | console.log |
クライアントIP+フルURLをログ出力(PII) | プライバシー |
| S5 | SELECT MAX(id)+1 |
採番の原子性なし → 並行リクエストでid/code重複 | 並行性 |
| S6 | INSERT |
await漏れのfire-and-forget。順序保証なし+unhandled rejection | 正確性 |
| S7 |
cache (Map) |
無効化もサイズ上限もない → 陳腐化+メモリ無限増加 | 正確性/設計 |
| S8 | hits = read+1 |
非アトミックUPDATE → 並行でlost update | 並行性 |
| S9 | catch (e) |
エラーを握り潰し { ok: true } を返し成功を偽装 |
信頼性 |
| S10 | res.redirect(url) |
URL未検証 → オープンリダイレクト/フィッシング | セキュリティ |
| S11 | rows[0] |
コード不在時に例外。GET側は try/catch なし | 正確性 |
| S12 | 採番/エンドポイント | 連番コードで列挙可能+認証・レート制限なし | 設計/ドメイン |
| S13 | ハンドラ全体 | レイヤリング・トランザクション境界なし | 設計 |
| S14 | 全体 | catch-allルート、マジックナンバー、エラー形状不統一 | 軽微 |
| S15 | Number(...max) |
JS Numberで 2^53 超の精度喪失(bigint未使用) | 正確性 |
4つのレビュー方式
| 条件 | コマンド | 測るもの |
|---|---|---|
| A. Claude自由記述 | 観点明示プロンプト | レビュー観点を明示して、広めにレビューを依頼した場合 |
B. Claude /code-review
|
/code-review shortener.ts |
Anthropic作り込みのレビュー手順 |
C. Codex /codex:review
|
/codex:review |
Codexの通常レビュー |
| D. Codex敵対的レビュー | /codex:adversarial-review |
実装方針・設計判断・信頼境界への踏み込み |
A の固定プロンプトは以下の通り。
shortener.ts を 正確性 / セキュリティ / 並行性 / プライバシー / 設計の観点でレビューし、具体的な指摘を file:line 付きの箇条書きで出してください。
このため、A は単に「Claudeに自由記述で聞いた場合」ではなく、正確には「レビュー観点を明示して、広めにレビューを依頼した場合」として扱っている。
公平性のルール
- 同一ファイル・同一観点・各2回。毎回 新規セッション→実行→出力保存→破棄で独立性を確保。
- モデルと effort を開示(Claude Opus/high、Codex gpt-5.5/high)。
- 採点は仕込み15項目を ◎(検出)/ △(部分)/ ✗(未検出)で評価し、誤検知も別記録。
ただし後述する通り、Codex 側はワーキングツリー全体に目を向ける場面があり、対象ファイル単体を見た Claude 自由記述とはスコープが完全には揃っていない。この点は今回の検証の限界として扱う。
結果
カテゴリ別検出数(≥1run / 仕込み数)
| カテゴリ | 仕込み | A | B | C | D |
|---|---|---|---|---|---|
| 正確性/並行処理 | 7 | 6 | 6 | 6 | 5 |
| セキュリティ | 3 | 3 | 3 | 3 | 1 |
| プライバシー | 1 | 1 | 1 | 1 | 0 |
| 設計/ドメイン | 2 | 2 | 0 | 0 | 0 |
| 信頼性/その他 | 2 | 2 | 2 | 1 | 0 |
| 計 | 15 | 14 | 12 | 11 | 6 |
代表的な指摘の違い
全員が拾ったもの
encode の小数除算バグは、4条件すべてが「id = id / ALPHABET.length に Math.floor がなく、小数インデックスで undefined が混入する」と的確に指摘した。SQLインジェクション、採番レース、await漏れも同様に全員が検出した。
このあたりのコアな正確性・並行処理バグについては、大きな差がつかなかった。
Claude自由記述だけが拾ったもの
差が出たのは設計/ドメイン寄りの指摘だった。
連番IDをそのまま base62 化しているため「コードを列挙すれば他人のURLを覗ける」という列挙攻撃の指摘、および「ルーティング・DBアクセス・採番・キャッシュが1ファイルに密結合していて層分離がない」という設計指摘は、Claude自由記述のみが出した。
/code-review・/codex:review・/codex:adversarial-review は、いずれもこの2点を見送った。
仕込み外で見つかった実問題
/codex:review の2回目と /codex:adversarial-review の両runは、shortener.ts ではなくローカル設定(例: .claude/settings.local.json)の Read 権限が広すぎる点を「信頼境界の越境」として指摘した。
これは仕込んだ15個のバグには含めていなかったが、実運用上はかなり意味のある発見だった。
考察
今回の結果から見えたのは、「ClaudeとCodexのどちらがレビューに強いか」という単純な話ではなく、レビュー方式によって拾いやすい指摘が変わるということだった。
正確性・並行処理(encode、採番レース、await漏れ、lost update、null crash)は全条件がほぼ互角で、少なくとも今回の検証では、ここに大きなモデル差は見られなかった。
効いていたのはモデルより「レビュー方式」
差が出たのは設計/ドメインで、ここを拾えたのは Claude自由記述のみだった。
ただし、これは「Claudeモデルが設計に強い」というより、観点明示の自由記述レビュー vs 専用コマンドのスコープ限定レビュー という方式差が主因だと考えている。根拠は3つある。
-
A vs B(同一モデル)
どちらも Claude(Opus)なのに、観点を明示した自由記述(A)は14件、/code-review(B)は12件で、設計系(S12 列挙可能性 / S13 レイヤリング)を落とした。ここから、同じモデルでも、レビュー観点をどこまで明示するか、あるいは専用コマンドのハーネスが何を対象にしているかで、拾う指摘が変わることが分かる。 -
B ≒ C(異モデル・近い方式)
/code-reviewと/codex:reviewは検出セットがかなり近く、どちらも設計系を見送った。モデルは違っても、追加設定なしで確信度の高いバグを返すレビューコマンドとして、似た挙動を示した。 -
effort を max に上げても設計系は出なかった(追試)
当初は「/code-reviewが件数を絞っている、つまり recall を捨てているから設計系を落とすのでは」と考えた。そこで/code-review shortener.ts maxを回したが、結果は依然として正確性バグ+セキュリティ+クリーンアップの同じレーンで、S12・S13 は未検出だった。
つまり差の原因は、単なる件数の絞り込みではなくスコープにありそうだ。/code-review は、正確性バグや reuse / simplification / efficiency のクリーンアップに寄ったレビューであり、脅威モデルやドメイン設計はそもそも守備範囲外なのだと思う。
max 版は、件数を増やすというより、指摘の確信度や検証の厚みを増す方向に働いていた。
敵対的レビューは別の軸
/codex:adversarial-review は、仕込んだバグの検出数だけを見ると最少だった。ただし、これは通常レビューの下位互換というより、そもそも見ている軸が違う。
実際、/codex:review の一部runと敵対的レビューでは、shortener.ts の外にあるローカル設定(例: .claude/settings.local.json)の Read 権限が広すぎる点まで踏み込み、信頼境界の問題として指摘した。
これは、通常の「このファイルに何個バグがあるか」という観点では拾いにくい指摘である。敵対的レビューは、バグの recall を広げるというより、実装方針・前提・信頼境界を揺さぶる用途で効くと考えた方がよさそうだ。
正直な注意点
-
スコープが非対称
Codex が.claudeファイルを指摘したのは、対象ファイル単体ではなくワーキングツリー全体を見ていた証拠でもある。shortener.ts限定で聞いた Claude自由記述とは前提が完全には揃っていない。 -
S15(Number の 2^53 精度)は全条件が未検出
現実には2^53件規模で初めて発火する理論エッジであり、今回の比較対象としては弱かった可能性が高い。 -
明確な誤検知はほぼ見られなかった
少なくとも今回の出力では、どの条件も的外れな指摘はほぼ出さず、品質自体は総じて高かった。 -
n=2(max は n=1)
集合のばらつきは観測されたが、確定的な結論には更なる試行が要る。特に max-effort の追試は1サンプルなので、スコープ説をより固めるには複数回の再実行が望ましい。
実運用への落とし込み
今回の結果から、AIコードレビューは「どのモデルを使うか」だけでなく、「どういうレビュー方式で聞くか」を分けて考えた方がよいと感じた。
バグの一次検出
/code-review や /codex:review のような純正コマンドが速くて高シグナルだった。正確性・並行性・セキュリティの明らかなバグを拾う用途では十分に強い。
PR前のセルフチェックや、実装後のゲートとして挟むにはかなり使いやすい。
設計・ドメインのレビュー
設計やドメインの妥当性まで見たい場合は、自由記述プロンプト、あるいは人間レビューを組み合わせた方がよい。
純正コマンドは、スコープ上バグ+クリーンアップに寄るため、「この設計でよいか」「このドメインで危ない前提はないか」までは任せきれない。
たとえば以下のように、観点を明示して聞いた方がよい。
正確性 / セキュリティ / 並行性 / プライバシー / 設計 / ドメイン前提の観点でレビューしてください。
実装バグだけでなく、仕様・脅威モデル・運用上のリスクも含めて指摘してください。
クロスプロバイダの二次レビュー
codex-plugin-cc で別モデルに通すと、同じClaude Codeのセッション内にいながら、別プロバイダの視点を入れられる。
これは単に「もう1つのモデルに聞く」というより、レビューの前提をずらす効果がある。特に、信頼境界や設定ファイルのように、対象コードの外側にあるリスクを見つける可能性があるのは面白い。
敵対的レビュー
敵対的レビューは、通常レビューの代替というより、設計判断や信頼境界を揺さぶる用途で使うのがよさそうだ。
「この実装にバグはいくつあるか」ではなく、「この実装方針はそもそも危なくないか」「攻撃者ならどこを見るか」「権限や設定の前提は妥当か」を見たい時に向いている。
まとめ
「ClaudeとCodex、どちらのレビューが鋭いか」と問うと、少し本質を外す。
今回の小さな検証では、検出結果を左右していたのはモデル名そのものよりも、レビューの聞き方だった。観点を明示して広く聞くと設計・ドメイン寄りの指摘まで拾いやすく、純正レビューコマンドは確信度の高いバグやクリーンアップに寄る。さらに敵対的レビューは、通常のバグ検出とは別に、信頼境界や前提の妥当性を見る用途で効きそうだった。
codex-plugin-cc の面白さは、Claude Code の中にいながら、こうしたレビュー方式の違いを手軽に試せるところにある。
バグ検出は純正コマンド、設計レビューは観点を明示した自由記述や人間レビュー、前提の揺さぶりは敵対的レビュー、と使い分けると、AIコードレビューの取りこぼしを減らせそうだ。