TL;DR

  • 使用 OpenAI 官方的 codex-plugin-cc,可以在 Claude Code 的工作階段中請 Codex 進行程式碼審查。
  • Claude Code 端有 /code-review,Codex 端則有 /codex:review/codex:adversarial-review,可以改變審查的提問方式。
  • 這次故意準備了一個埋入 15 個 bug 的 URL 縮短服務,並針對 4 種審查方式各測試 2 次。
  • 結果顯示,檢出數的差異,比起「是 Claude 還是 Codex」,更可能受到「是否明確指定觀點、廣泛地提問」或「用專用指令縮小範圍提問」的影響。
  • 但這只是 n=2 的小型驗證,不能用來下結論判定模型能力高下。

檢出數(2 次中至少 1 次檢出 / 共 15 個):

條件方式檢出數Claude 自由描述有明確觀點的自由描述提示詞14Claude /code-review專用指令12Codex /codex:review專用指令11Codex /codex:adversarial-review專用指令(敵對式審查)6另外,/codex:adversarial-review 不是一般的 bug 全面檢查,而是會深入實作方針、設計判斷、信任邊界的審查,因此單純用檢出數來評價並不容易。

環境:Claude Opus / effort high、Codex gpt-5.5 / effort high。


codex-plugin-cc 是什麼

codex-plugin-ccOpenAI 官方 的外掛,可以把 Codex 接進 Claude Code,讓你不用離開 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 種不同的 bug。設計上涵蓋正確性、並行性、安全性、隱私與設計等類別。

// 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 token(範例)
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);

埋入的 bug(節錄):

  • 正確性/並行處理:encode 少了 Math.floorINSERT 少了 await、SELECT MAX(id)+1 編號競爭條件、hits 的 lost update、code 不存在時的 rows[0] 當機、Number 的 2^53 精度問題
  • 安全性:SQL Injection、token 直接寫死、Open Redirect
  • 隱私:輸出 IP + URL 到 log
  • 設計/領域:連號 code 可被枚舉且沒有 rate limit、缺乏分層、catch 吞掉錯誤並偽裝成功

埋入的 15 個 bug

ID位置內容類別S1encode() id = id / ALPHABET.length 沒有 Math.floor,導致變成非整數。code 會壞掉正確性S2各查詢把 url / code 直接插入模板字串 → SQL Injection安全性S3ADMIN_TOKEN 機密直接寫在原始碼中(而且未使用)安全性S4console.log 印出客戶端 IP + 完整 URL(PII)隱私S5SELECT MAX(id)+1 沒有原子性 → 併發請求時 id/code 重複並行性S6INSERT 少了 await 的 fire-and-forget。沒有順序保證 + 可能出現 unhandled rejection正確性S7cache (Map) 沒有失效機制也沒有大小上限 → 過期資料 + 記憶體無限成長正確性/設計S8hits = read+1 非原子 UPDATE → 併發時 lost update並行性S9catch (e) 吞掉錯誤並回傳 { ok: true },偽裝成功可靠性S10res.redirect(url) 未驗證 URL → Open Redirect / 釣魚安全性S11rows[0] 在 code 不存在時會拋例外。GET 端沒有 try/catch正確性S12採編號/端點的連號 code 可被枚舉,且沒有驗證與 rate limit設計/領域S13整個 handler 沒有分層、也沒有交易邊界設計S14全域 catch-all route、magic number、錯誤格式不一致等小問題輕微S15Number(...max) 使用 JS Number,超過 2^53 會喪失精度(未使用 bigint)正確性### 4 種審查方式

條件指令測量項目A. Claude 自由描述觀點明示提示詞明確指定審查觀點,請求較廣泛的審查B. Claude /code-review``/code-review shortener.tsAnthropic 內建的審查流程C. Codex /codex:review``/codex:reviewCodex 的一般審查D. Codex 敵對式審查/codex:adversarial-review深入實作方針、設計判斷與信任邊界A 的固定提示詞如下:

請從正確性 / 安全性 / 並行性 / 隱私 / 設計 的角度審查 shortener.ts,並以帶有 file:line 的條列式方式提出具體指摘。

因此,A 不是單純的「對 Claude 自由提問」,而是更精確地說:「明確指定審查觀點,請求較廣泛的審查」。

公平性規則

  • 同一檔案、同一觀點、各 2 次。每次都以 新工作階段→執行→保存輸出→丟棄 的方式確保獨立性。
  • 公開模型與 effort(Claude Opus/high、Codex gpt-5.5/high)。
  • 評分方式是把埋入的 15 項以 ◎(檢出)/ △(部分)/ ✗(未檢出)評估,並另外記錄誤判。

但如後文所述,Codex 端有時會注意到整個工作樹的內容,因此它所看到的範圍,和只針對 shortener.ts 的 Claude 自由描述並不完全相同。這一點是本次驗證的限制之一。

結果

各類別檢出數(≥1 run / 埋入數)

類別埋入ABCD正確性/並行處理76665安全性33331隱私11110設計/領域22000可靠性/其他22210總計1514**12116**### 代表性指摘差異

所有人都抓到的項目
encode 的小數除法 bug,四種條件都正確指出為「id = id / ALPHABET.length 沒有 Math.floor,會因為小數索引而混入 undefined」。SQL Injection、編號競爭條件、await 漏掉也同樣都被抓到。

這一類核心的正確性與並行處理 bug,幾乎沒有明顯差異。

只有 Claude 自由描述抓到的項目
差異出現在設計/領域相關的指摘。

將連號 ID 直接轉成 base62,因此「只要枚舉 code 就能偷看別人的 URL」的枚舉攻擊,以及「路由、DB 存取、編號、快取全部在同一個檔案中緊密耦合,缺乏分層」這個設計指摘,只有 Claude 自由描述有提出。

/code-review/codex:review/codex:adversarial-review 都沒有提到這兩點。

在埋入項目之外找到的實際問題
/codex:review 的第 2 次與 /codex:adversarial-review 的兩次,除了 shortener.ts 之外,還指出本機設定(例如 .claude/settings.local.json)的 Read 權限過寬,屬於「信任邊界越界」。

這不在埋入的 15 個 bug 內,但對實際運作來說是相當有意義的發現。

考察

從這次結果可以看出,重點不是單純比較「Claude 和 Codex 哪個模型比較會審查」,而是不同審查方式會影響它們容易抓到的問題。

正確性/並行處理(encode、編號競爭條件、await 遺漏、lost update、null crash)在所有條件下都差不多,至少這次測試中看不出明顯的模型差距。

真正影響較大的是「審查方式」而不是模型

差異出現在設計/領域,而能抓到這些的是只有 Claude 自由描述。

不過,這與其說是「Claude 模型比較擅長設計」,不如說是明確觀點的自由描述審查 vs 專用指令的範圍限制審查 這種方式差異造成的。理由有三點:

  1. A vs B(同一模型)
    兩者都用 Claude(Opus),但明確指定觀點的自由描述(A)抓到 14 項,/code-review(B)只有 12 項,並漏掉設計類(S12 可枚舉性 / S13 分層)。這表示即使是同一模型,只要你明確說明要看的觀點,或專用指令的外殼到底限制了什麼,抓到的指摘就會不同。
  2. B ≒ C(不同模型、相近方式)
    /code-review/codex:review 的檢出集合很接近,兩者都沒有碰設計類。即使模型不同,只要都是「不加額外設定、回傳高信心 bug」的審查指令,行為就很像。
  3. 即使把 effort 調到 max,設計類還是沒出現(追加實驗)
    一開始我以為 /code-review 是把數量壓低,也就是犧牲 recall,所以才漏掉設計類。於是我又跑了 /code-review shortener.ts max,結果仍然是同樣的正確性 bug + 安全性 + cleanup 那條線,S12、S13 還是沒檢出。

所以差異的原因,似乎不是單純的數量限制,而是「範圍」本身。/code-review 比較像是偏向正確性 bug、reuse / simplification / efficiency 的 cleanup 型審查,而脅威模型或領域設計本來就不在它的守備範圍內。

max 版本比較像是提高指摘的把握度與驗證深度,而不是單純增加數量。

敵對式審查是另一個軸線

/codex:adversarial-review 如果只看埋入 bug 的檢出數,確實是最少的。不過它不是一般審查的低配版,而是本來就看不同面向。

實際上,/codex:review 的部分 run 以及敵對式審查,都會進一步指出 shortener.ts 之外的本機設定(例如 .claude/settings.local.json)Read 權限過寬,並把它視為信任邊界問題。

這種指摘不是單純看「這個檔案有幾個 bug」就容易發現的。敵對式審查與其說是擴大 bug recall,不如說更適合用來挑戰實作方針、前提與信任邊界。

幾個誠實的注意事項

  • 範圍不對稱
    Codex 會指出 .claude 檔案,證明它看到的不只是目標檔案,而是整個工作樹。這與只針對 shortener.ts 的 Claude 自由描述並不完全是同一個前提。
  • S15(Number 的 2^53 精度)所有條件都沒檢出
    現實中要到 2^53 等級的資料量才會觸發,作為本次比較對象可能太理論化了。
  • 幾乎沒有明顯誤判
    至少在這次輸出中,各條件都沒有出現太離譜的指摘,整體品質普遍不錯。
  • n=2(max 版本只有 n=1)
    雖然觀察到集合差異,但還不足以做決定性結論。尤其 max-effort 的追試只有 1 次,若要更有力支持「是範圍因素」的推論,還需要更多重複實驗。

實務上的落地方式

從這次結果來看,AI 程式碼審查不只是「選哪個模型」,也要把「用什麼方式提問」分開考慮。

bug 的初步檢出

/code-review/codex:review 這類原生指令速度快,而且訊號高。用來抓明顯的正確性、並行性、安全性 bug,非常夠力。

放在 PR 前的自我檢查,或實作完成後的門檻檢查,都很好用。

設計與領域審查

如果你想看的是設計或領域合理性,最好搭配自由描述提示詞,或直接加上人工審查。

原生指令的範圍偏向 bug + cleanup,因此不能完全期待它去回答「這個設計好不好」、「這個領域假設有沒有危險」。

例如可以像這樣把觀點說清楚再請它審查:

請從正確性 / 安全性 / 並行性 / 隱私 / 設計 / 領域前提的角度審查。
不只指出實作 bug,也請包含規格、威脅模型與營運風險。

跨供應商的二次審查

透過 codex-plugin-cc 把內容交給另一個模型,就能在同一個 Claude Code 工作階段內加入不同供應商的視角。

這不只是「再問一個模型」而已,而是會改變審查前提。特別是像信任邊界、設定檔這種超出目標程式碼本身的風險,反而更容易被看見,這點很有趣。

敵對式審查

敵對式審查比較適合拿來挑戰設計判斷與信任邊界,而不是取代一般審查。

它不是在問「這個實作有幾個 bug」,而是在問「這個實作方針本身危不危險」、「攻擊者會看哪裡」、「權限與設定前提是否合理」。

總結

如果問「Claude 和 Codex 哪個審查比較銳利」,其實有點偏離重點。

這次這個小型驗證顯示,左右結果的與其說是模型名稱,不如說是審查方式。當你明確指定觀點並廣泛提問時,比較容易抓到設計與領域層面的指摘;原生審查指令則偏向高信心的 bug 與 cleanup;而敵對式審查則更適合用來檢視信任邊界與前提是否合理。

codex-plugin-cc 的有趣之處,就在於你可以在 Claude Code 裡很方便地試這些不同的審查方式。

如果把工具分工清楚:bug 初檢用原生指令,設計審查用明確觀點的自由描述或人工審查,前提挑戰用敵對式審查,AI 程式碼審查的漏網之魚應該可以減少不少。


原文出處:https://qiita.com/kunitomo926/items/7dfc85770cf5132f3d1c


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝18   💬3   ❤️5
750
🥈
我愛JS
📝2   💬5   ❤️3
143
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登