AIDive

Claude Code의 검증은 PASS라고 했다, 코드는 고장나 있었다

AIDive · 게시

코딩 에이전트AI 모델

그 자신을 채점했다: PASS

Claude Code에 내장된 검증 기능인 /verify가 실제로는 망가진 기능에 PASS 판정을 내렸다. 이는 실제 저장소에서 진행한 116개 Claude Code 세션의 벤치마크 결과로, 모든 "done"은 에이전트가 한 번도 보지 못한 인수 테스트로 나중에 채점되었다.

Claude Code를 만든 Boris Cherny는 검증이야말로 에이전트에게 줄 수 있는 가장 중요한 것이라고 말한다. 내장된 검증 기능은 Claude Code v2.1.215부터 요청 시에만 실행된다: /verify를 직접 입력하지 않으면 실행되지 않는다. 이번 벤치마크에서는 24번 실행 중 24번 모두 PASS라고 답했고, 그중 하나는 실제로 망가진 기능을 그대로 통과시킨 경우였다. 실제로 그 버그를 잡아낸 것이 무엇이었는지는 아래에서 다루며, 비용이 2.5배나 들었지만 아무것도 바꾸지 못한 설정도 함께 소개한다.

벤치마크: 116회 실행, 본 적 없는 테스트

가짜 "done"이란, 숨겨진 인수 테스트나 저장소 자체의 테스트 스위트는 실패하는데도 작업이 끝났다고 주장하며 종료되는 세션을 말한다. 이번 벤치마크는 여섯 가지 검증 설정에서 이런 일이 얼마나 자주 일어나는지 측정한다.

이런 의심은 Claude Code 자체의 이슈 트래커에서 시작됐다. 2026-09-23에 등록된 issue #96416은 19가지 우려 사항을 나열한 리뷰가 그중 5개만 확인하고도 "accept as is"라는 결론을 내렸다고 설명한다.

Parameter Value
저장소 msiemens/tinydb (Python 문서 데이터베이스), 커밋 18d73a1
저장소 테스트 스위트 226개
기능 요청 6건, 각각 5개의 요구사항 명시
숨겨진 인수 테스트 요구사항당 1개, 실행 전에 작성, 에이전트에게 공개되지 않음
모델 Opus 5.5, Sonnet 5, Haiku 4.5
Claude Code 2.1.283, 헤드리스(claude -p), 60턴 상한
세션 태스크 실행 112회 + 교차 모델 /verify 실행 4회 = 116
지출 API 환산 $66.29

여섯 가지 설정은 순정 Claude Code(요청만)부터 종료 시점에 두 번째 모델이 작업을 점검하는 방식까지 이어진다:

Setup What it adds
A 순정 없음
B /verify 두 번째 턴으로 입력하는 내장 /verify
C verify skill Anthropic의 워크플로에 따라 작성한 프로젝트 skill
D Stop hook 테스트 스위트가 실패 상태인 동안 종료를 막고 요구사항별 증거를 요구하는 스크립트
E tests first CLAUDE.md 규칙: 코드를 작성하기 전에 요구사항마다 실패하는 테스트부터 작성
F Opus verifier 변경 사항에 대해 Opus로 /verify를 실행하는 Stop hook

테스트가 잘 갖춰진 라이브러리에 명확한 요청을 하는 것은 쉬운 경우다. 순정 Opus 5.5는 12번의 실행 모두 정답을 냈고, 그중 11번은 done이라고 말하기 전에 스스로 테스트 스위트를 실행했다.

내장된 /verify: 매번 PASS, 2.5배

/verify는 Claude Code에 기본 탑재된 검증용 skill이다. 변경 사항을 실행하고 diff를 읽은 뒤 단계별 판정을 작성한다. v2.1.215부터는 사용자가 직접 호출해야만 실행되므로, 이번 벤치마크에서는 모든 태스크가 끝난 뒤 두 번째 턴으로 입력했다.

세 모델을 통틀어 24번 실행 중 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가 실제 버그를 하나 찾아내긴 했다. Opus의 한 태스크에서 라이브러리에 원래부터 있던 reuse-after-close 문제를 지적했는데, 이는 검사 중이던 변경 사항 때문에 생긴 것이 아니었다. 이미 제대로 되어 있는 작업에 대해서는, 비싼 두 번째 의견일 뿐이다.

Skill 또는 hook: 건너뛰어지는 쪽

skill은 Claude가 관련 있다고 판단할 때 열어보는 마크다운 절차서다. 검증 루프에 관한 Anthropic의 블로그 글은 6단계 레시피를 소개한다: 가장 자주 손으로 하는 후속 확인 작업을 고른다, 먼저 내장된 /verify를 시도한다, 그 절차를 평이한 영어로 적는다, 이를 skill로 만든다, 그리고 새 태스크에 적용해보며 다듬는다. 이번 벤치마크의 skill인 verify-change는 정확히 이 방식으로 만들어졌으며, done이라고 말하기 전에 실제 코드를 기준으로 모든 요구사항을 증명하라고 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이 유일하게 가짜 "done"을 낸 세션은 skill이 한 번도 열리지 않은 세션이었다. 그 세션은 자신이 새로 작성한 테스트 두 개가 실패한 채로 끝났다.

Stop hook은 에이전트가 종료를 시도할 때마다 Claude Code가 실행하는 스크립트다. 종료를 거부하고 이유를 알려 에이전트를 다시 작업으로 돌려보낼 수 있다. 이번 벤치마크의 hook은 테스트 스위트를 실행해 실패 상태인 동안 종료를 막고, 첫 종료 시도에서 요구사항별로 한 줄씩 증거를 요구한다. 이 hook은 모든 세션에서 작동했다. Opus에서는 비용이 20% 더 들어 태스크당 $0.39 대신 $0.47이 들었다(75초 대신 94초). 이번 벤치마크에서는 종료 시점에 실패한 테스트 스위트를 한 번도 만나지 못했기 때문에 잡아낼 것이 없었다. hook은 항상 작동하지만, 당신이 확인하라고 지정한 것만 확인한다.

사각지대: 11개 중 11개 놓침

실패한 태스크는 테이블에 고유 필드를 요구했다: 두 사용자가 같은 이메일을 공유할 수 없고, 두 문서가 같은 고유 값을 갖게 되는 업데이트는 반드시 DuplicateKeyError를 발생시켜야 한다는 것이다.

Haiku 4.5는 한 사용자를 다른 사용자의 이메일로 옮기는 경우를 테스트했고, 이는 올바르게 거부되었다. 하지만 여러 문서에 일치하는 하나의 업데이트가 동일한 새 이메일을 모든 문서에 쓰는 경우는 한 번도 테스트하지 않았다. 이 경우는 순정, /verify, skill, Stop hook, tests first 등 같은 모델을 쓴 모든 설정에서, Haiku의 11개 세션 중 11개 모두 오류 없이 그대로 통과했다.

Haiku 자신의 /verify는 자신이 시도해본 경우에만 체크 표시를 하고 PASS라고 적었다. 숨겨진 테스트는 DID NOT RAISE DuplicateKeyError를 보고했다. 같은 변경 사항에 /verify를 실행해 달라고 요청받은 Sonnet 5 역시 PASS를 반환했다. Opus와 Sonnet은 둘 다 이 기능을 스스로 올바르게 작성했으므로, 이는 하나의 모델이 하나의 태스크에서 보인 사례일 뿐이지만, 같은 모델이 작성한 점검은 그 모델의 사각지대를 그대로 공유한다.

외부 체크: Opus는 FAIL

같은 Haiku의 변경 사항에 대해 Opus 5.5가 실행한 동일한 /verify는 3번 중 3번 모두 FAIL을 반환했고, 매번 놓친 경우를 정확히 짚어냈다: 여러 문서에 일치하는 하나의 업데이트가 오류 없이 모든 문서에 같은 값을 쓴다는 것이다. 이 버그를 잡아낸 것은 작성자도, 작성자 자신의 점검도 아닌, 다른 모델이었다.

Stop hook으로 연결하면(설정 F), Haiku가 종료를 시도할 때마다 Opus가 그 작업을 점검한다. 결과는 다음과 같다:

T6, per run Haiku + Opus checker Opus 5.5 alone
버그 수정 / 가짜 "done" 3번 중 3번 수정 가짜 "done" 0회
턴 모든 실행에서 60턴 상한에 도달
비용 checker 포함 $1.36 $0.76

외부 점검은 효과가 있다. 다만 이 태스크에서는 더 강한 모델이 혼자서 기능을 작성하는 것보다 비용이 더 많이 들었다.

따라할 것, 그리고 비용

변경 사항을 작성한 모델이 그것을 점검하는 유일한 존재가 되도록 두지 마라.

Rule Why Cost on this bench
스크립트로 확인 가능한 것에는 Stop hook을 유지하라 모든 세션에서 작동한다 Opus 기준 약 20% 추가
실제 점검은 작성자 바깥에서 오도록 하라: 관문에 더 강한 모델을 두거나, 요청 내용으로부터 직접 작성한 테스트를 사용하라 같은 모델은 자신의 버그를 11번 중 11번 놓쳤다 Haiku + Opus checker 사용 시 태스크당 $1.36
Opus에 명확한 요청을 할 때는 /verify를 입력하지 마라 바뀐 결과 0건 비용 2.5배, 75초 대신 125초

한계: 작은 라이브러리 하나, 명확한 요청 6건, 셀당 1~3회 실행, 헤드리스 세션, 그리고 각 요청이 명시한 것만 확인하는 숨겨진 테스트. 더 지저분한 작업에서는 수치가 달라질 것이다.

출처

자주 묻는 질문

Claude Code의 /verify는 버그를 잡아내나요?
같은 모델이 자기 작업을 점검할 때는 믿을 만하지 않습니다. 116개 세션 벤치마크에서 /verify는 24번 중 24번 모두 PASS를 반환했는데, 여기에는 명시된 요구사항을 어긴 Haiku 4.5의 변경도 포함됩니다. 같은 변경에 대해 Opus 5.5가 실행하자 3번 중 3번 모두 FAIL을 반환했습니다.
Opus와 함께 쓸 때 Claude Code의 /verify는 쓸 가치가 있나요?
명확한 요청에서는 그렇지 않습니다. Opus 5.5에서는 태스크당 비용을 $0.39에서 $0.96으로(2.5배) 올리고 시간도 75초에서 125초로 늘렸지만, 12건의 결과 중 어느 것도 바꾸지 못했습니다. 순정 Opus가 이미 12번 중 11번은 스스로 테스트 스위트를 실행했기 때문입니다.
검증에는 Claude Code skill과 Stop hook 중 무엇을 써야 하나요?
스크립트로 확인할 수 있는 것에는 Stop hook을 쓰세요. Claude는 검증 skill을 24개 세션 중 10개에서만 열었지만(Opus 12개 중 8개, Sonnet 6개 중 2개, Haiku 6개 중 0개), Stop hook은 에이전트가 종료를 시도할 때마다 실행됩니다. 비용은 Opus 기준 약 20% 더 들었습니다.
Claude Code의 Stop hook이란 무엇인가요?
에이전트가 종료를 시도할 때마다 Claude Code가 실행하는 스크립트입니다. block이라는 JSON decision과 이유를 함께 반환해 종료를 거부하고 에이전트를 다시 작업으로 돌려보낼 수 있습니다. 스크립트가 테스트하는 내용만 확인합니다.
Claude Code에서 더 강한 모델이 더 약한 모델의 코드를 점검할 수 있나요?
네. Stop hook으로 연결한 Opus 5.5의 /verify는 Haiku 4.5가 놓친 경우를 3번 중 3번 모두 고치게 만들었습니다. 비용은 컸습니다: 모든 실행이 60턴 상한에 도달했고 평균 $1.36이 들었는데, 이는 Opus가 혼자 기능을 작성할 때의 $0.76보다 많습니다.
AI 모델은 왜 자신의 코드에서 버그를 놓치나요?
자신의 점검은 이미 생각해본 경우만 테스트하기 때문입니다. Haiku는 한 사용자를 다른 사용자의 이메일로 옮기는 경우는 확인했지만, 하나의 업데이트가 여러 문서에 영향을 주는 경우는 한 번도 확인하지 않았습니다. 그래서 자신의 /verify는 시도해본 경우에만 체크하고 PASS라고 적었지만, 숨겨진 테스트는 실패했습니다.

관련 영상