它自己批改自己:PASS
Claude Code 內建的驗證檢查 /verify,在一個其實壞掉的功能上回傳了 PASS。這是在一個真實程式庫上跑了 116 次 Claude Code session 後得到的結果,每一次「完成」事後都用 agent 從未看過的驗收測試來評分。
Boris Cherny,Claude Code 的創造者,稱驗證是你能給它的最重要的東西。這個內建檢查自 Claude Code v2.1.215 起只能手動觸發:你打 /verify,不然它就不會跑。在這次測試中,它在 24 次執行裡全部回傳 PASS,其中一次還是已經出包的功能。真正抓到那個 bug 的是什麼,會在下面說明,還有那個貴了兩倍半、卻什麼都沒改變的設定。
測試方式:116 次任務,從沒看過的測試
一次假的「完成」,指的是某個 session 宣稱工作已經完成,但一個隱藏的驗收測試,或是程式庫自己的測試套件,卻失敗了。這次測試衡量的,就是在六種驗證設定下,這種情況發生的頻率。
背後的疑慮來自 Claude Code 自己的 issue tracker:2026-09-23 提出的 issue #96416,描述了一次列出 19 項疑慮、驗證了其中 5 項、卻仍然做出「照現狀接受」(accept as is)結論的審查。
| Parameter | Value |
|---|---|
| 程式庫 | msiemens/tinydb(Python 文件資料庫),commit 18d73a1 |
| 程式庫測試套件 | 226 個測試 |
| 功能需求 | 6 個,每個列出 5 項要求 |
| 隱藏驗收測試 | 每項要求一個,在任何一次執行之前就寫好,agent 從未看過 |
| 模型 | Opus 5.5、Sonnet 5、Haiku 4.5 |
| Claude Code | 2.1.283,headless 模式(claude -p),60 回合上限 |
| Sessions | 112 次任務執行 + 4 次跨模型 /verify 執行 = 116 |
| 花費 | 66.29 美元(API 等值) |
六種設定,從純 Claude Code(只丟出需求)到讓第二個模型在停下時檢查工作:
| Setup | What it adds |
|---|---|
| A plain | 什麼都沒加 |
| B /verify | 內建的 /verify,在第二輪手動輸入 |
| C verify skill | 依照 Anthropic 的流程寫的一個 project skill |
| D Stop hook | 一個 script,在測試套件是紅燈時阻擋停下,並要求針對每項要求提供證據 |
| E tests first | 一條 CLAUDE.md 規則:任何程式碼寫之前,先為每項要求寫一個會失敗的測試 |
| F Opus verifier | 一個 Stop hook,讓 Opus 對這次變更執行 /verify |
清楚的需求配上一個測試完善的函式庫,是最簡單的情況。純 Opus 5.5 十二次執行全部答對,而且有十一次是自己主動跑了測試套件才說完成。
內建的 /verify:每次都 PASS,貴 2.5 倍
/verify 是 Claude Code 內建的驗證 skill。它會跑一次變更、讀取 diff,然後逐步寫下判定結果。自 v2.1.215 起它只能由使用者觸發,所以在這次測試中,它是在每個任務之後,作為第二輪手動輸入執行的。
它在三個模型、24 次執行中全部回傳 PASS 判定,包括 Haiku 那個壞掉的變更。在 Opus 5.5 上,它沒有改變任何結果,卻多花了 2.5 倍的成本:
| Opus 5.5, per task | Plain | With /verify |
|---|---|---|
| 成本 | $0.39 | $0.96 |
| 實際耗時 | 75 秒 | 125 秒 |
| 結果改變 | 12 次中 0 次 |
平心而論,/verify 確實抓到了一個真的 bug:在一次 Opus 任務中,它標出了一個「重用已關閉物件」的問題,但那個問題本來就存在於函式庫裡,並非這次要檢查的變更造成的。在原本就寫對的工作上,它只是一個昂貴的第二意見。
Skill 還是 hook:哪一個會被跳過
skill 是一份 Claude 判斷相關時才會打開的 markdown 流程。Anthropic 部落格文章談驗證迴圈的那篇,描述了一套六步驟做法:挑出你最常手動做的後續檢查、先試試內建的 /verify、把流程寫成白話文字、把它變成一個 skill,然後在新任務上呼叫它並反覆迭代。這次測試用的 skill,verify-change,就是照這個方法建的,它會要求 Claude 在說完成之前,先對照真正的程式碼證明每一項要求。
skill 是一種建議,Claude 自己決定要不要打開它:
| Model | Sessions that opened the skill |
|---|---|
| Opus 5.5 | 12 次中 8 次 |
| Sonnet 5 | 6 次中 2 次 |
| Haiku 4.5 | 6 次中 0 次 |
| 總計 | 24 次中 10 次 |
唯一一次 Sonnet 假「完成」,來自一個從未打開 skill 的 session:它結束時,自己新寫的兩個測試都是失敗的。
Stop hook 是 Claude Code 每次 agent 嘗試結束時都會執行的一個 script。它可以拒絕結束,並附上理由把 agent 送回去。這次測試的 hook 會跑測試套件、在紅燈時擋下,並在第一次嘗試結束時要求針對每項要求提供一行證據。它在每一個 session 都有觸發。在 Opus 上,它多花了 20% 的成本,$0.47 對比 $0.39(94 秒對比 75 秒)。在這次測試中,它從未遇到過在停下那一刻是紅燈的測試套件,所以沒有東西可抓:hook 每次都會觸發,但它只檢查你告訴它要檢查的東西。
盲點:11 次裡漏掉 11 次
那個失敗的任務要求資料表上的欄位要唯一:兩個使用者不能共用同一個 email,任何會讓兩份文件變成同一個唯一值的更新,都必須拋出 DuplicateKeyError。
Haiku 4.5 測試了把一個使用者的 email 改成另一個使用者的 email,這個情況正確地被拒絕了。但它從沒測試過這種情況:一次更新同時符合多份文件,把同一個新 email 寫進所有文件裡。這種情況在 11 次 Haiku session 裡全部都直接通過、沒有出現錯誤,而且是在每一種同模型設定下都一樣:plain、/verify、skill、Stop hook 和 tests first。
Haiku 自己的 /verify 勾選了它試過的那個情況,寫下 PASS。隱藏測試回報的卻是 DID NOT RAISE DuplicateKeyError。讓 Sonnet 5 對同一個變更執行 /verify,也一樣回傳 PASS。Opus 和 Sonnet 都各自把這個功能寫對了,所以這只是一個模型在一項任務上的結果,但由同一個模型寫的檢查,會有同一個盲點。
外部檢查:Opus 判定 FAIL
同樣的 /verify,對同一個 Haiku 變更,這次改由 Opus 5.5 執行,結果是 3 次中 3 次都判定 FAIL,每一次都準確指出漏掉的那個情況:一次更新同時符合多份文件,卻把同一個值寫進所有文件裡而沒有出現錯誤。抓到這個問題的,是另一個模型,不是作者本人,也不是作者自己的檢查。
把它接成 Stop hook(設定 F)之後,每次 Haiku 嘗試結束時,Opus 都會檢查這次工作。結果如下:
| T6, per run | Haiku + Opus checker | Opus 5.5 alone |
|---|---|---|
| Bug 修好/假「完成」 | 3 次中 3 次修好 | 0 次假「完成」 |
| 回合數 | 每次執行都碰到 60 回合上限 | |
| 成本 | $1.36(含檢查者) | $0.76 |
外部檢查真的有用。但在這個任務上,它花的錢比讓更強的模型自己寫這個功能還要多。
可以照抄的做法,以及代價
別再讓寫出變更的那個模型,成為唯一檢查它的人。
| Rule | Why | Cost on this bench |
|---|---|---|
| 對任何 script 就能檢查的事,保留一個 Stop hook | 它每個 session 都會觸發 | 在 Opus 上大約多花 20% |
| 讓真正的檢查來自作者之外:在關卡上放一個更強的模型,或是你自己依照需求寫的測試 | 同一個模型自己漏掉同一個 bug,11 次裡漏了 11 次 | Haiku + Opus 檢查者,每個任務 $1.36 |
用 Opus 處理清楚的需求時,跳過手動輸入 /verify |
0 個結果改變 | 成本 2.5 倍,125 秒對比 75 秒 |
限制:一個小型函式庫、六個清楚的需求、每格只跑一到三次、headless session,而且隱藏測試只檢查每個需求裡明講的內容。換成更混亂的工作,這些數字會變。
AIDive