훅의 종료 코드 2와 셸 파이프의 함정
클로드 코드 테스트용 검사기가 분명히 “막음” 을 외쳤는데, 빈 폴더에서 다시 돌려 보니 AI 는 파일을 그대로 써 버렸습니다. 검사기가 끝나며 남긴 숫자가 1이면 지나가고 2일 때만 멈췄습니다. 앞 글 클로드 코드 백그라운드, 남의 글이 붙고 잠금이 남았다에서는 여러 작업이 잠금 파일을 같이 쓸 때 생긴 일을 적었습니다.
1. 클로드 코드 테스트 게이트에서 무슨 일이 있었나요?
블로그 대표 이미지 기준이 두 번 롤백됐습니다. 2026-09-17~18 에는 남색 템플릿이, 2026-09-27 에는 숫자 강조 템플릿이 다시 나왔습니다. 그래서 규칙을 지시문에서 빼내 검사기(정해진 기준과 다르면 막는 작은 프로그램)로 옮겼습니다.
그 검사기를 셸과 클로드 코드 훅(도구를 쓰기 직전에 끼어드는 스크립트) 두 곳에 걸었습니다. 2026-10-01 빈 폴더에서 같은 훅을 마지막 숫자만 바꿔 돌렸더니, 종료 코드(프로그램이 끝나며 남기는 성공, 실패 숫자) 1일 때는 hello.txt 가 생겼고 2일 때만 쓰기가 멈췄습니다.
2. 무슨 문제가 생겼나요?
검사기가 맞는 숫자를 내도, 그 숫자를 읽는 쪽이 막음으로 받아 주지 않으면 막히지 않습니다. 화면에는 “막음” 이 찍혀 있으니 사람은 막힌 줄 압니다.
이런 자리가 두 군데 있었습니다. 클로드 코드 2.1.281 의 PreToolUse 훅은 exit 2 만 막음으로 읽었습니다. 셸에서는 검사 출력을 남기려고 tee 파이프를 붙이자, 검사기의 1이 파이프 끝 tee 의 0에 덮여 올리기가 실행됐습니다.
훅 종료 코드
클로드 코드의 마지막 출력(로그 그대로)
hello.txt
exit 1
완료했습니다. hello.txt 파일이 생성되었고 “hi”가 한 줄 들어있습니다.
생김(hi)
exit 2
Write 도구가 gate.sh 훅에 의해 차단됨: 대표 이미지 기록이 기준과 다르다.
안 생김
3. 클로드 코드 테스트 게이트, 어떻게 풀었나요?
검사기를 고치기보다 숫자를 읽는 자리를 하나씩 맞췄습니다.
규칙을 지시문 밖으로 뺍니다. 정의 안의 “수치형이면 숫자 템플릿” 분기와 “실패하면 임시로 옛 방식” 문장을 지우고, 검사기가 이미지 기록(manifest) 첫 칸의 생성 도구와 배경을 직접 읽어 막게 했습니다(검사 항목 G07b)
검사기 종료 코드를 0, 1, 2 로 나눕니다. 기준과 같으면 0, 다르면 1, 파일을 못 열거나 JSON 이 깨졌으면 2입니다. 판정 불가를 통과로 두지 않는 것이 가장 먼저 정한 줄입니다
클로드 코드 훅에서는 막을 경우를 전부 exit 2 로 냅니다.Claude Code 훅 문서도 “Exit 2 means a blocking error.” 라고 적습니다
셸 파이프에 tee 를 쓰면 pipefail 을 켭니다. 켠 서브셸에서는 같은 줄이 “멈춤(종료 코드 1)” 로 돌아왔습니다
막히면 기준을 낮추지 않고 산출물을 고칩니다. bad.json 을 기준에 맞게 다시 쓰자 같은 검사기에서 통과였습니다
거는 자리
막음으로 읽는 코드
새어 나간 경우
맞춘 방법
셸 검사 && 올리기
0 이 아닌 전부
없음
그대로 둔다
셸 파이프 끝에 tee
맨 끝 tee 의 코드
막음 1 이 통과로 바뀜
set -o pipefail
클로드 코드 PreToolUse 훅
exit 2 만
exit 1 로 끝난 훅
막을 경우를 exit 2 로
허용 값을 하나 더 넣는 것이 가장 빠른 길이지만, 그러면 지운 분기가 검사기 안에 다시 생깁니다. 규칙을 알려 줘도 안 지켜진 일은 AI에게 규칙을 알려줬는데 안 지켜졌습니다에서 다뤘습니다.
4. 누구에게 도움이 되나요?
에이전트가 만든 산출물을 검사기로 거르고, 그 검사기를 셸과 클로드 코드 훅에 같이 거는 분이라면 읽어 보세요. 막는 분기 하나와 파이프 한 줄만 보면 됩니다. 셸에서 검사 뒤에 바로 이어 붙이기만 하고 파이프를 쓰지 않는다면 굳이 손볼 필요는 없습니다. 그 자리에서는 1이든 2든 멈췄습니다.
검증까지가 일이라는 이야기는 AI 에이전트로 혼자 일하기, 늘어난 건 산출물이 아니라 검증이었습니다에, 되돌릴 수 있다는 믿음의 비용은 AI 에이전트 권한 설계와 되돌리기 비용에 적었습니다.
5. 한 줄 정리
필자의 결론
클로드 코드 테스트 게이트는 검사기가 맞는 숫자를 내는 것만으로는 막지 못하고, 그 숫자를 읽는 자리가 막음으로 받아 줘야 막습니다. 그래서 검사기는 0, 1, 2 로 고정하고 셸에서는 pipefail, 훅에서는 막을 경우를 전부 exit 2 로 냅니다. 남은 구멍도 있습니다. 검사기는 기록만 보고 이미지 실물은 보지 않으며, 누군가 기준 값을 바꾸면 바뀐 기준으로 충실히 통과시킵니다.
그래서 당신은 이것만 하면 됩니다. 지금 걸어 둔 훅 스크립트 하나를 열어 막는 분기가 exit 2 로 끝나는지, tee 를 쓰는 줄에 pipefail 이 켜져 있는지 확인해 보세요.
2026-10-01 macOS 의 /tmp 빈 폴더에서 Python 3.14.7, 클로드 코드 2.1.281 로 돌렸습니다. 각 폴더의 .claude/settings.json 에는 {"hooks":{"PreToolUse":[{"matcher":"Write","hooks":[{"type":"command","command":"<작업 폴더>/gate.sh"}]}]}} 한 줄을 두었고, gate.sh 는 아래와 같습니다(exit 1 판은 마지막 숫자만 다름).
#!/bin/bash
echo "막음: 대표 이미지 기록이 기준과 다르다" >&2
exit 2
클로드 코드에는 claude -p --model haiku --setting-sources project --allowedTools "Write" 로 “hello.txt 파일을 만들고 hi 한 줄을 써. 막히면 막힌 이유를 한 줄로 적고 다른 방법은 쓰지 마.” 를 넘겼습니다. exit 1 폴더의 실제 출력입니다.
== 훅이 exit 1 로 끝날 때 (/tmp/s5h-wzAd)
완료했습니다. hello.txt 파일이 생성되었고 "hi"가 한 줄 들어있습니다.
hello.txt: 생김(hi)
검사기 checkhero.py 는 ok.json 에 “통과”(0), bad.json 에 “막음: generator=stat-template, background=solid”(1), broken.json 에 “판정 불가: JSONDecodeError”(2), missing.json 에 “판정 불가: FileNotFoundError”(2) 를 냈습니다. 셸 실험은 python3 checkhero.py "$f" && publish 로 돌렸고 publish 는 “올리기 실행됨” 만 찍는 셸 함수라 실제 글은 건드리지 않았습니다. tee 를 붙인 줄의 출력입니다.
haiku 로 한 번씩 돌린 결과라 다른 이벤트와 도구는 미확인이고, 훅 안에서 tee 를 쓰는 경우도 재현하지 않았습니다. bash 버전은 로그에 남기지 않아 미확인이고, exit 1 판에서 훅이 불린 흔적(표준 오류 문구)은 출력에 보이지 않아 같은 구성의 exit 2 판으로 미뤄 볼 뿐입니다.