Hooks 종료 코드로 도구 실행 차단하기
훅 스크립트의 종료 코드는 Claude Code의 다음 동작을 결정하는 신호입니다. 0은 허가, 2는 차단, 그 외는 에러이며, PreToolUse 훅에서 2를 반환해 rm -rf 같은 위험 명령을 막는 방법을 다룹니다.
훅 스크립트가 반환하는 종료 코드는 Claude Code가 다음에 무엇을 할지 결정하는 신호입니다. PreToolUse 훅에서 종료 코드 2를 반환하면 그 도구 호출 자체가 차단되므로, 위험한 작업을 막는 '가드레일'을 만들 수 있습니다. PreToolUse·PostToolUse 훅에서 다룬 사전 체크를 실제 차단으로 발전시키는 단계로, ~/Desktop/hooks-test 디렉터리를 이어서 씁니다.
핵심 요약
- 종료 코드 0은 훅의 '이의 없음'을 뜻할 뿐이며, PreToolUse에서는 통상적인 권한 플로(확인 프롬프트 등)가 계속 적용됩니다.
- 종료 코드 2는 차단이며, PreToolUse뿐 아니라 많은 이벤트에서 처리를 저지합니다(PostToolUse 등 일부 제외).
- 그 외의 종료 코드는 에러 취급입니다.
테스트 디렉터리 준비
아직 만들지 않았다면 아래 명령으로 만듭니다.
mkdir -p ~/Desktop/hooks-test/.claude
cd ~/Desktop/hooks-test종료 코드 0·2·그 외는 각각 무엇을 뜻하나
훅에서 실행되는 명령은 끝날 때 '종료 코드'라는 숫자를 반환하고, 이 값에 따라 Claude Code의 동작이 달라집니다.
| 종료 코드 | 의미 | 동작 |
|---|---|---|
| 0 | 성공 | 도구 실행을 허가 |
| 2 | 차단 | 도구 실행을 저지(PreToolUse뿐 아니라 많은 이벤트에서 유효. PostToolUse 등 일부 제외) |
| 그 외 | 에러 | 통상 모드에서도 stderr(표준 에러 출력)의 내용이 표시됨(non-blocking이므로 명령 자체는 실행됨) |
가장 중요한 것은 종료 코드 2입니다. PreToolUse 훅에서 2를 반환하면 그 도구 호출 자체가 차단됩니다.
종료 코드 2에 의한 차단은 PreToolUse뿐 아니라 UserPromptSubmit·Stop·SubagentStop·PreCompact·WorktreeCreate 등 많은 이벤트에서 유효합니다. 다만 PostToolUse는 예외로, 2를 반환해도 단순한 에러로 취급될 뿐 차단 효과는 없습니다. 이미 실행이 끝난 뒤이기 때문입니다.
먼저 모든 Bash 명령을 차단해 보기
동작 원리를 확인하기 위해 모든 Bash 명령을 차단하는 가장 단순한 훅부터 만듭니다. ~/Desktop/hooks-test/.claude/settings.json에 아래 설정을 추가합니다.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo '차단: Bash 명령 실행은 허가되지 않았습니다' >&2; exit 2"
}
]
}
]
}
}~/Desktop/hooks-test에서 Claude Code를 실행하고 "ls를 실행해줘"라고 요청하면 다음처럼 차단됩니다.
> ls를 실행해줘
⏺ Bash(ls)
⎿ Error: PreToolUse:Bash hook error: [echo '차단: Bash 명령 실행은 허가되지 않았습니다' >&2; exit 2]: 차단:
Bash 명령 실행은 허가되지 않았습니다
⏺ Bash 명령이 hook에 의해 차단됐습니다.>&2로 stderr에 적은 메시지가 그대로 Claude에게 전달되고, exit 2가 도구 실행을 막았습니다. 이 상태로 두면 모든 Bash 명령이 막히니, 확인이 끝나면 설정을 지우거나 다음 실전 예로 교체하세요.
실전: rm -rf 만 차단하기
더 실전적인 예로, 폴더째 삭제하는 rm -rf는 막되 일반 rm file.txt는 허용하는 훅을 만듭니다.
- 검증용 프로젝트의 hooks 디렉터리에 스크립트를 만듭니다. 표준 입력의 JSON에서 명령을 꺼내
-r이나-f옵션이 있으면 차단합니다.
#!/bin/bash
# ~/Desktop/hooks-test/.claude/hooks/block-rm-rf.sh
# rm -rf 를 차단 (rm 은 허가)
# 표준 입력에서 JSON을 읽고 명령을 추출
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // empty')
# -r 이나 -f 옵션이 있으면 차단 (rm file.txt 는 허가)
if [[ "$command" =~ -[rf] ]]; then
echo "차단: rm -rf / rm -r 은 허가되지 않았습니다" >&2
exit 2
fi
exit 0- 실행 권한을 부여합니다.
chmod +x ~/Desktop/hooks-test/.claude/hooks/block-rm-rf.shsettings.json에서 이 스크립트를 PreToolUse 훅에 등록합니다. matcher는Bash로 두고, 명령 내용의 판단은 스크립트 안에서 합니다. 매처 패턴의 상세는 Hooks 매처를 참고하세요.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/Desktop/hooks-test/.claude/hooks/block-rm-rf.sh"
}
]
}
]
}
}- 삭제 테스트용 폴더와 파일을 만들어 둡니다.
mkdir test-folder
touch test-folder/test.txt- Claude Code를 실행하고
rm -rf test-folder를 요청하면 다음처럼 차단됩니다.
> `rm -rf test-folder` 를 실행해줘
⏺ I'll execute that command to remove the test-folder directory.
⏺ Bash(rm -rf test-folder)
⎿ Error: PreToolUse:Bash hook error: [~/Desktop/hooks-test/.claude/hooks/block-rm-rf.sh]: 차단:
rm -rf / rm -r 은 허가되지 않았습니다
⏺ Bash 명령이 hook에 의해 차단됐습니다. rm -rf / rm -r의 실행은 허가되지 않았습니다.
통상적인 파일 삭제가 필요한 경우엔 다른 방법을 시도해 주세요. 예를 들어 파일 탐색기에서 삭제하거나 다른 도구를 사용해 주세요.Error: PreToolUse:Bash hook error: [...]: 차단: ... 부분이 종료 코드 2로 인한 차단입니다. stderr에 적은 이유가 전달됐기 때문에 Claude는 막힌 이유를 이해하고 다른 방법을 제안합니다. 실수로 중요한 파일을 지울 위험이 크게 줄어드니 훨씬 과감하게 작업을 맡길 수 있습니다.
permissions 와 훅 차단은 어떻게 가려 쓰나
단순한 Bash 명령 가드라면 퍼미션 설정의 deny 규칙만으로도 충분합니다. 옵션 조합처럼 명령 내용을 해석해서 판단해야 하는 복잡한 가드가 필요할 때 PreToolUse 훅에서 종료 코드 2를 반환하는 방식이 빛을 발합니다. 두 방법은 대체 관계가 아니라 층을 이루는 관계이므로, 기본 가드는 permissions에 두고 세밀한 조건은 훅으로 보강하는 식으로 조합하세요.
정리
- 종료 코드 0은 성공이며 도구 실행을 허가합니다(PreToolUse에서는 통상 권한 플로가 계속 적용).
- 종료 코드 2는 차단이며 PreToolUse 외 많은 이벤트에서 유효하지만, PostToolUse에서는 에러로만 취급됩니다.
- 그 외 종료 코드는 에러 취급으로 stderr가 표시될 뿐 실행은 막지 않습니다.
- 차단 이유는
>&2로 stderr에 적어야 Claude가 다른 방법을 찾을 수 있습니다. - 먼저 "rm -rf 차단"부터 시작해 가드레일을 넓혀 가고, 스크립트에서 프로젝트 정보와 파일 경로를 얻는 방법은 Hooks 환경 변수에서 이어집니다.
자주 묻는 질문
팀에 Claude Code를 도입하려면 실제 코드베이스에 맞춘 설계가 필요합니다.
무료 상담 신청