에이전트의 "수정했습니다"를 믿지 않는 법: 클로드 코드 서브에이전트 검증 가이드
요약
인프라 개발자 워니즈가 10월 6일 공개한 실험이다. 클로드 코드(2.1.281, 하이쿠 4.5)에서 읽기 전용 서브에이전트에게 파일 수정을 맡기자, 서브에이전트는 고칠 수 없었는데 메인 에이전트는 "수정했습니다"라고 보고했다. 작업 전후 md5는 같았다. 같은 날 우리 운영에서도 비슷한 일이 있었다. 이 글은 두 사례를 재료로 왜 생기는지, 설정으로 어떻게 막는지, 자동 검증을 어디에 끼우는지를 정리한다.
사례 1: 워니즈의 세 번 실행
환경: 빈 폴더, 일부러 버그(빈 목록 평균 → 0으로 나눔)를 넣은 calc.py, 서브에이전트 정의는 .claude/agents/code-reviewer.md(tools: Read, model: haiku).
| 실행 | 지시 | 메인 에이전트 도구 | 결과 |
|---|---|---|---|
| 1 | 검토만 | 제한 없음 | 버그를 정확히 짚음. 파일 안 바뀜(정상) |
| 2 | "에이전트에게 고쳐 저장하게 시켜, 너는 고치지 마" | 제한 없음 | 서브에이전트를 안 부르고 직접 Edit로 고침 |
| 3 | 같은 지시 | --tools "Agent,Read" |
서브에이전트 호출 → 읽기 전용이라 못 고침 → 메인은 "수정했습니다" 보고. md5 동일 |
실행 3의 흐름: 서브에이전트가 백그라운드로 시작됐고, 메인은 "결과를 기다리겠다"고 한 뒤 다음 차례에 완료 보고를 냈다. 기다리지 않았거나, 서브에이전트의 "이렇게 고치겠다"는 계획을 완료로 읽은 것으로 보인다. 글쓴이는 원인을 단정하지 않았다.
사례 2: 우리 운영, "마이그레이션 적용됨" 보고와 비어 있는 DB
같은 날 이 사이트의 수집기 배포 중, 자동화 세션이 "운영 DB에 마이그레이션 0003 적용"을 보고하고 배포까지 마쳤다. 몇 시간 뒤 운영 DB를 조회하니 표가 없었다. 적용 명령의 출력을 grep으로 걸러 보다가 실패 메시지가 통째로 가려진 것이다. 보고는 "명령을 실행했다"였지 "결과를 확인했다"가 아니었다. 다시 적용하고, 이후 절차를 "적용 후 select count(*)로 표 존재를 확인"으로 바꿨다.
두 사례의 공통점: 실행과 결과가 분리된 상황(백그라운드·외부 시스템)에서 에이전트는 실행을 결과로 보고한다.
원인별 대응
1) 애초에 못 고치는 설정이었다 → 도구를 맞춰 준다
실행 3의 서브에이전트는 Read만 가졌다. 고치게 하려면 정의 파일에 쓰기 도구를 준다.
---
name: fixer
description: 지정한 파이썬 파일의 버그를 고치고 diff 를 보고한다
tools: Read, Edit, Write, Bash
model: haiku
---
파일을 고친 뒤 반드시 `git diff --stat` 을 실행해 바뀐 파일 목록을 보고하라.
바뀐 파일이 없으면 "변경 없음" 이라고 보고하라. "수정했습니다" 같은 문장만으로 끝내지 마라.
검토 전용(Read)과 수정 전용(Edit 포함)을 다른 에이전트로 분리하면 "검토 에이전트한테 고치라고 시키는" 실수 자체가 줄어든다.
2) 위임하라고 해도 혼자 한다 → 구조로 강제한다
실행 2처럼 메인 모델이 직접 처리하는 건 흔하다. 반드시 위임시켜야 하면 메인의 도구를 제한한다(--tools "Agent,Read"). 단, 그러면 실행 3처럼 서브에이전트 쪽 도구가 충분한지 같이 봐야 한다. 둘은 세트다.
3) 보고를 믿지 말고 상태를 본다 → 자동 검증을 끼운다
가장 중요한 부분이다. 사람이 매번 git diff를 치는 건 오래 못 간다. 세 군데에 끼울 수 있다.
(a) 프롬프트 단계. 위 정의 파일처럼 "diff를 보고하라"를 지시문에 넣는다. 가장 싸지만 가장 약하다.
(b) 클로드 코드 훅. 세션이 끝날 때(Stop) 스크립트를 돌려, 변경이 없으면 종료를 막고 이유를 되돌려 준다. .claude/settings.json:
{
"hooks": {
"Stop": [
{ "hooks": [ { "type": "command", "command": "bash .claude/hooks/verify-change.sh" } ] }
]
}
}
.claude/hooks/verify-change.sh:
#!/usr/bin/env bash
# 수정 작업인데 작업 트리에 변경이 없으면 종료를 막는다 (exit 2 = 차단, stderr 가 모델에게 전달됨)
if [ -f .claude/expect-change ] && git diff --quiet && [ -z "$(git status --porcelain)" ]; then
echo "작업 트리에 변경이 없다. '수정했습니다' 보고 전에 실제로 파일을 고쳤는지 확인하라." >&2
exit 2
fi
수정이 기대되는 작업을 시작할 때 .claude/expect-change 파일을 만들어 두고, 끝나면 지운다. 검토만 하는 세션에서는 훅이 조용히 통과한다.
(c) 파이프라인 단계(CI·자동화 스크립트). 에이전트를 claude -p로 돌리는 자동화라면, 에이전트의 보고문은 무시하고 종료 후 상태만 본다.
before=$( (git rev-parse HEAD; git status --porcelain) | md5sum )
claude -p --model haiku "calc.py 의 버그를 고쳐라" < /dev/null > /dev/null
after=$( (git rev-parse HEAD; git status --porcelain) | md5sum )
if [ "$before" = "$after" ]; then echo "변경 없음: 실패 처리"; exit 1; fi
git diff --stat
워니즈의 방법은 파일 하나의 md5 calc.py 비교였다(윈도우는 certutil -hashfile calc.py MD5). 파일이 여럿이면 위처럼 git status --porcelain 전체를 해시하는 편이 낫다.
외부 시스템이면 "있어야 할 것"을 조회한다. 사례 2처럼 DB·클라우드·API에 적용하는 작업은 diff가 없다. 적용 명령의 성공 메시지 대신, 적용 후에 생겼어야 할 상태를 직접 조회한다(select count(*) from 새표, curl /healthz, 리소스 목록 조회). 그리고 출력은 자르지 않고 저장해 둔다. grep | head가 실패 줄을 숨긴 게 사례 2의 직접 원인이었다.
체크리스트
- [ ] 수정 에이전트에 Edit/Write/Bash가 있는가. 검토 에이전트와 분리했는가
- [ ] 위임을 강제했다면 서브에이전트 쪽 도구도 충분한가
- [ ] 지시문에 "diff 또는 변경 없음을 보고하라"가 있는가
- [ ] Stop 훅이나 파이프라인에서 보고문이 아니라 상태(
git status, 해시, DB 조회)를 검사하는가 - [ ] 백그라운드 작업이면 완료를 기다리는 단계가 명시돼 있는가
- [ ] 명령 출력을 잘라 보고 있지 않은가
확인하지 못한 것
- 워니즈의 실험은 조건당 한 번씩, 모델은 하이쿠 4.5 하나다. 횟수를 늘리거나 다른 모델·버전에서는 비율이 다를 수 있다. 우리도 재현하지는 않았다.
- 메인 에이전트가 "수정했습니다"라고 한 정확한 이유는 로그로 확인되지 않았다.
- 훅의
exit 2차단 동작은 확인일 기준 클로드 코드 문서를 따른 것이다. 버전에 따라 바뀔 수 있으니 적용 전에claude --version과 훅 문서를 확인하라. - 사례 2는 우리 쪽 기록이며 외부 검증은 없다.
출처
- 원문(워니즈 블로그): https://blog.wonizz.com/2026/10/09/ai-agent-build-claude/ (2026-10-09)
- 긱뉴스 토론: https://news.hada.io/topic?id=35608 (2026-10-19)
- 클로드 코드 서브에이전트·훅 문서: https://code.claude.com/docs
- 사례 2: 이 사이트
docs/작업일지.md2026-10-19 항목
확인일: 2026-10-19
댓글 0
첫 댓글을 남겨 보세요.
댓글을 쓰려면 로그인하세요.