Claude Code가 완료에 대해 거짓말하는 것을 방지하는 방법 — 50줄의 bash hook
요약
Claude Code가 작업 완료를 낙관적으로 보고하여 발생하는 회귀(regression) 문제를 해결하기 위해 50줄의 bash hook을 활용한 워크플로우 가드 방안을 제시합니다. 이 스크립트는 모델이 실제 검증 로그를 남기기 전까지 세션을 종료하지 못하도록 강제하여 작업의 신뢰성을 높입니다.
핵심 포인트
- Claude Code의 과도한 낙관주의로 인한 '테스트 통과 보고 후 운영 환경 에러' 문제 해결
- 프롬프트 수정 대신 Stop hook을 활용한 워크플로우 가드(Workflow Guard) 방식 채택
- bash 스크립트를 통해 실제 검증(verification) 로그가 기록되었는지 확인 후 세션 종료 허용
- 비정상 종료 코드(exit 2)를 사용하여 모델이 오류 메시지를 컨텍스트로 인식하고 작업을 지속하게 유도
14개의 병렬 프로젝트에서 12개월 동안 Claude Code를 실행한 결과, 동일한 퇴보(regression) 사이클이 계속 반복되었습니다: Claude: "모든 테스트 통과 ✅" 나: [PR 머지] 운영 환경(Prod): [500 에러 발생] 나: [2시간 동안 디버깅] 내일: [동일한 사이클 반복]. 모델이 의도적으로 거짓말을 하는 것이 아닙니다. 단지 자신의 작업에 대해 지나치게 낙관적일 뿐입니다. 해결책은 더 나은 프롬프트(prompt)가 아닙니다. 해결책은 워크플로우 가드(workflow guard)입니다. 그래서 저는 verify-before-stop.sh를 만들었습니다. 이는 실제 검증(verification)이 로그에 기록될 때까지 세션이 종료되는 것을 차단하는 Stop hook입니다. 전체 구현 내용과 이것이 작동하는 이유를 소개합니다. 패턴: Claude Code의 Stop hook은 모델이 세션을 종료하려고 할 때 실행됩니다. 이 hook은 stdin을 통해 stop_hook_active, transcript_path, session_id가 포함된 JSON을 전달받습니다. 만약 hook이 stderr 메시지와 함께 0이 아닌 종료 코드(non-zero exit)로 종료되면, Claude는 턴(turn)을 계속하며, 해당 stderr는 모델의 컨텍스트(context)의 일부가 됩니다. 이것이 바로 활용 지점(leverage point)입니다. 만약 모델이 증거 없이 "완료(done)"라고 주장한다면, 어떤 증거가 필요한지에 대한 지침과 함께 exit 2로 종료하십시오.
hook (50줄)
#!/bin/bash
verify-before-stop.sh
INPUT = $( cat )
무한 루프 방지 — Claude Code는 이전 블록에서 계속할 때 stop_hook_active=true를 설정합니다
STOP_HOOK_ACTIVE = $( echo " $INPUT " | python3 -c
"import sys,json; d=json.load(sys.stdin); print('true' if d.get('stop_hook_active') else 'false')"
2>/dev/null )
if [ " $STOP_HOOK_ACTIVE " = "true" ] ;
then exit 0
fi
VERIFY_LOG = ".claude/state/stop-verify.log"
mkdir -p .claude/state
파일 변경 없음 → 순수 대화 → 종료 허용
HAS_CHANGES = $( git diff --name-only 2>/dev/null | head -5 )
HAS_UNTRACKED = $( git ls-files --others --exclude-standard 2>/dev/null | grep -v '.claude/state/' | head -5 )
if [ -z " $HAS_CHANGES " ] && [ -z " $HAS_UNTRACKED " ] ;
then exit 0
fi
파일 변경됨 → 최근 5분 이내에 VERIFIED 로그 항목 필요
if [ -f " $VERIFY_LOG " ] ;
then
FIVE_MIN_AGO = $( date -v-5M +%s 2>/dev/null || date -d '5 minutes ago' +%s 2>/dev/null || echo 0 )
LAST_VERIFY = $( grep '|VERIFIED' " $VERIFY_LOG " 2>/dev/null | tail -1 | cut -d '|' -f1 )
LAST_ACTION = $(
grep '|VERIFY_ACTION' " $VERIFY_LOG " 2>/dev/null | tail -1 | cut -d '|' -f1 ) if [ -n " $LAST_VERIFY " ] && [ " $LAST_VERIFY " -gt " $FIVE_MIN_AGO " ] 2>/dev/null ; then if [ -n " $LAST_ACTION " ] && [ " $LAST_ACTION " -gt " $FIVE_MIN_AGO " ] 2>/dev/null ; then echo " $( date +%s ) |STOP_ALLOWED" >> " $VERIFY_LOG " exit 0 fi fi fi echo " $( date +%s ) |STOP_BLOCKED" >> " $VERIFY_LOG " echo "⛔ BLOCKED: files changed but no verification logged in last 5 min." > &2 echo "Required: log a VERIFY_ACTION + VERIFIED entry, e.g.:" > &2 echo ' echo "$(date +%s)|VERIFY_ACTION|ran npm test, 3 failures" >> .claude/state/stop-verify.log' > &2 echo ' echo "$(date +%s)|VERIFIED" >> .claude/state/stop-verify.log' > &2 exit 2 설치 방법: .claude/hooks/에 넣고 연결하세요: // .claude/settings.json { "hooks" : { "Stop" : [{ "matcher" : "*" , "hooks" : [ { "type" : "command" , "command" : "bash .claude/hooks/verify-before-stop.sh" } ] }] } } Claude Code 세션을 다시 시작하세요. 완료. 어떻게 검증하
Claude Code는 exit 2를 stderr가 모델에 전달되는 Stop 블록으로 취급합니다. exit 1은 일반적인 오류로 취급되어 메시지가 올바르게 나타나지 않을 수 있습니다.
- 왜 명시적인 VERIFY_ACTION + VERIFIED 2줄 패턴을 사용하는가?
VERIFY_ACTION 라인은 모델이 단순히 주장만 하는 것이 아니라, 검증한 내용을 글로 직접 기록하도록 강제합니다. 이는 나중에 grep으로 검색할 수 있는 감사 추적 (audit trail) 역할도 겸합니다.
주의사항: Claude Code v2.1.143+ 버전에서 추가된 제한 사항이 있습니다. 약 8회의 연속적인 Stop-hook 블록이 발생하면, 결과와 상관없이 경고와 함께 턴 (turn)이 종료됩니다. 이는 의도된 설계로, 고장 난 훅 (hook)이 모델을 무한 루프에 빠뜨리는 것을 방지합니다. 만약 더 많은 재시도가 필요한 정당한 이유가 있다면 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP=20 설정을 통해 이를 재정의할 수 있습니다. 훅을 설계할 때는 8번째 턴이 아니라, 다음 턴에 모델이 준수할 수 있도록 충분한 정보를 제공하도록 만드세요.
이것이 저를 구한 방법
14개의 프로젝트에서 6개월 동안 이 훅을 사용하며 얻은 효과:
- "AI는 테스트가 통과했다고 하지만, 실제로는 통과하지 않은" 회귀 (regression) 현상을 제거했습니다. 이는 "잠깐, 아까 이거 된다고 하지 않았나요?"라는 말이 나오는 머지 (merge)의 가장 큰 원인이었습니다.
- 명시적인 검증 로깅을 강제했습니다. 이제 .claude/state/stop-verify.log는 마치 감사 추적 기록처럼 읽힙니다.
- 대화 압축 (conversation compaction) 상황에서도 살아남습니다. 로그 파일은 영구적으로 유지되므로, Claude Code가 세션을 요약한 후에도 검증 기록은 디스크에 남아 있습니다.
오픈 소스
GitHub 전체 소스: https://github.com/ianymu/claude-verify-before-stop
MIT 라이선스. 유용하다고 생각되면 Star를 눌러주세요. 나중에 다시 필요할 때 찾기 쉬워집니다.
나머지 내용이 궁금하시다면, 저는 지난 12개월 동안 만든 5개의 훅을 이 훅과 함께 패키지로 구성했습니다: cost-tracker.sh (Opus 사용 금액을 매번 costs.jsonl에 기록), block-secrets.sh (커밋 전 Write/Edit/Bash에서 sk-ant-* / JWT / AWS 키를 스캔), force-progress-update.sh (압축에 대비해 5번의 작업마다 체크포인트 생성), pre-compact-diary.sh (진행 중인 작업(WIP) 컨텍스트 보존), enforce-autoplan.sh (계획 없이는 코드 작성 불가).
전체 6종 패키지는 출시 가격으로 $49(정가 $79)이며, 30일 환불 보장 정책이 적용됩니다. Polar를 통해 즉시 다운로드할 수 있습니다: https://landing-ianymu.vercel.app 하지만 솔직히 말씀드리면, verify-before-stop 하나만으로도 고통의 80%를 해결할 수 있습니다. 거기서부터 시작해 보세요. 일주일 동안 무료로 사용해 본 후, 나머지 기능들도 그만한 가치가 있는지 결정하시기 바랍니다.
토론: 여러분도 이와 유사한 워크플로우 가드(workflow guards)를 구축해 보셨는지 궁금합니다. verify-before-stop 패턴은 지나고 보면 당연해 보이지만, 여러분은 어떤 다른 "완료에 대한 거짓말(lies of completion)" 패턴을 발견하셨나요? 여러분만의 훅(hooks)을 댓글로 남겨주세요. 내용이 훌륭하다면 출처를 명시하여 README에 추가하도록 하겠습니다. — Ian
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기