Claude Code 훅, exit 1로는 못 막는다: exit 2만 '차단'으로 인정되는 이유와 셸 파이프 함정

한눈에 보는 핵심 요약
Claude Code 훅으로 테스트 게이트를 만들 때 종료 코드 1과 2는 결과가 완전히 다릅니다.
- exit 1: 검사기가 "막음"을 출력하고 끝나도 Claude Code는 도구 실행을 막지 않습니다.
- exit 2: 공식 문서 표현대로 "Exit 2 means a blocking error." 이 코드로 끝나야 실제로 차단됩니다.
- 일반 셸 스크립트는 반대로 0이 아닌 코드를 모두 실패로 처리합니다. 다만
| tee처럼 파이프를 쓰면 검사기의 실패 코드가 사라질 수 있습니다.
같은 검사기라도 어디서 실행되느냐에 따라 "실패"의 뜻이 달라집니다. 그래서 훅을 만들 때 이 차이를 알고 있어야 합니다.
실험: exit 1로 끝나는 훅은 Write를 막았을까?
원문 작성자는 PreToolUse(Write) 훅에 검사 스크립트를 걸고 아래 명령을 실행했습니다.
claude -p --model haiku --setting-sources project --allowedTools "Write" \
"hello.txt 파일을 만들고 hi 한 줄을 써. 막히면 막힌 이유를 한 줄로 적고 다른 방법은 쓰지 마."
훅이 exit 1로 끝난 경우
완료했습니다. hello.txt 파일이 생성되었고 "hi"가 한 줄 들어있습니다.
hello.txt: 생김(hi)
훅이 "막음"이라고 출력했는데도 모델은 작업을 끝냈다고 답했고 파일도 만들어졌습니다. 게이트가 아무것도 막지 못한 것입니다.
훅이 exit 2로 끝난 경우
같은 스크립트에서 마지막 종료 코드만 2로 바꾸자 Write가 차단됐습니다. 공식 훅 문서에 적힌 동작과 같습니다.
💡 정리하면 Claude Code 훅에서 차단 신호는 exit 2 하나뿐입니다. exit 1을 포함한 다른 0이 아닌 코드는 오류로 기록될 수는 있어도 도구 실행을 멈추지는 않습니다.
셸에서 생기는 또 다른 함정: 파이프와 tee
셸 스크립트나 CI에서는 0이 아닌 종료 코드가 모두 실패로 처리됩니다. 여기까지는 예상대로입니다. 문제는 검사 결과를 파일로 남기려고 tee를 붙였을 때 생겼습니다.
./check.sh | tee gate.log && ./upload.sh
파이프라인의 종료 코드는 기본적으로 마지막 명령의 종료 코드입니다. 그래서
check.sh가 1(막음)로 끝나도tee가 0으로 끝나면- 파이프라인 전체가 성공으로 처리되고
- 실행되면 안 되는 올리기 단계가 실행됐습니다.
해결: set -o pipefail
set -o pipefail
./check.sh | tee gate.log && ./upload.sh
pipefail을 켜면 파이프라인 중 하나라도 실패했을 때 전체가 실패로 처리됩니다. 로그를 tee로 남기는 게이트 스크립트라면 이 옵션을 켜 두는 편이 안전합니다.
작성자가 정한 규칙
원문 작성자는 실험 결과를 바탕으로 아래처럼 규칙을 고정했습니다.
1. 검사기의 종료 코드 의미를 하나로 고정
| 종료 코드 | 의미 |
|---|---|
| 0 | 통과 |
| 1 | 막음 |
| 2 | 판정 불가 |
2. 실행 환경별로 맞추기
- 셸 / CI:
set -o pipefail을 켜서 파이프 끝의tee가 실패 코드를 덮지 못하게 합니다. - Claude Code 훅: 막아야 하는 경우(검사기의 1과 2)는 모두
exit 2로 바꿔서 내보냅니다.
훅 래퍼는 예를 들어 이렇게 쓸 수 있습니다.
#!/usr/bin/env bash
set -o pipefail
./check.sh 2>&1 | tee -a gate.log
rc=$?
if [ "$rc" -ne 0 ]; then
echo "테스트 게이트 실패 (code=$rc)" >&2
exit 2 # Claude Code는 exit 2만 차단으로 처리함
fi
exit 0
검사기 쪽 의미(통과/막음/판정 불가)는 그대로 두고, 어떻게 막을지는 실행 환경에 맞게 래퍼에서 처리하는 방식입니다.
알아둘 한계
작성자가 밝힌 재현 범위는 다음과 같습니다.
- 버전: Claude Code 2.1.281
- 모델: haiku
- 훅 이벤트:
PreToolUse(Write)한 가지 - 반복 횟수: 경우별 1회
다른 훅 이벤트(PostToolUse, Stop, UserPromptSubmit 등)에서 exit 1과 2가 어떻게 동작하는지는 확인하지 않았습니다. 다른 이벤트에 게이트를 걸 계획이라면 직접 재현해 보고 공식 훅 문서에서 해당 이벤트의 종료 코드 동작도 확인하시길 권합니다.
마무리
AI 코딩 에이전트에 "테스트가 실패하면 진행하지 마"라는 게이트를 걸 때는 그 게이트가 실제로 막는지 확인해야 합니다. 검사기가 실패를 출력했다고 해서 차단까지 된다는 보장은 없습니다.
- Claude Code 훅에서 차단하려면 exit 2를 쓰세요.
- 셸 파이프라인에는 **
set -o pipefail**을 켜세요. - 검사기의 종료 코드 의미는 한 가지로 고정하고, 환경별 변환은 래퍼에서 처리하세요.
게이트를 만들었다면 일부러 실패하는 경우를 한 번 실행해서 정말 막히는지 직접 확인해 보는 것을 추천합니다.
원문: https://news.hada.io/topic?id=36873&utm_source=geeknews&utm_medium=rss&utm_campaign=news