
Claude Code의 hook이 지켜지지 않는 10가지 케이스 — 15개를 실운용하며 겪은 제한 사항 모음
요약
Claude Code의 hook 메커니즘을 실운용하며 발견한 10가지 제한 사항과 보안 취약점을 분석합니다. exit code 기반 차단의 불완전성, Windows 환경의 차이, 서브 에이전트 적용 불가 등 실제 발생 가능한 예외 케이스를 다룹니다.
핵심 포인트
- exit 2 대신 permissionDecision: 'deny'를 사용하여 차단을 명시할 것
- Windows 환경에서는 exit 2를 통한 파일 편집 차단이 작동하지 않을 수 있음
- 서브 에이전트에는 PreToolUse hook이 적용되지 않으므로 별도 프롬프트 제약 필요
- deny-list는 명령어 이름만 막으므로 의미적 의도를 고려한 설계가 필요함
- 보호 설정은 'ask'가 아닌 'deny' 기반으로 정의해야 안전함
서론
Claude Code의 hook은 "AI에게 100% 준수시키고 싶은 규칙을 기계적으로 강제하기" 위한 메커니즘입니다. 입문용 기사는 일어와 영어 모두 늘어났습니다 (예: 모든 도구 호출을 인터셉트하기, 저장 시마다 auto-format/auto-test/self-heal 수행).
반면 잘 쓰이지 않는 내용이 바로 "hook이 지켜지지 않는 케이스"입니다. 저는 15개의 hook을 상용하고 있으며, "차단했다고 생각했는데 차단되지 않았던" 상황을 여러 번 겪었습니다. 이 기사는 그 제한 사항의 목록입니다. hook을 방어 계층으로 사용하려면, 할 수 있는 것보다 할 수 없는 것을 먼저 알아야 합니다.
차단했다고 생각했지만 되지 않은 케이스
1. exit 2는 Task/Bash를 차단하지 않는다
PreToolUse hook의 exit 2는 일부 도구에 대해 효과가 없습니다 (#26923). 차단 목적의 hook은 exit code가 아니라 permissionDecision: "deny"를 JSON으로 반환하고 exit 0으로 통일하는 것이 안전합니다.
2. Windows에서는 exit 2가 Edit조차 차단하지 않는다
Windows 환경에서는 PreToolUse의 exit 2로 파일 편집이 그대로 실행되며, 나아가 PostToolUse의 stderr가 모델에 전달되지 않는(피드백으로 기능하지 않는) 것이 측정되었습니다 (#80039). Windows를 병용하는 팀은 "Mac에서는 작동하는데 Windows에서는 그냥 통과된다"는 상황이 발생할 수 있음을 전제로, deny 기반으로 설정을 맞추어야 합니다.
3. exit 2의 효과는 이벤트에 따라 다르다
동일한 exit 2라도, UserPromptSubmit에서는 모델 호출 전에 완전 차단(과금 발생 없음)되는 반면, SessionStart에서는 무시되어 세션이 진행되고 과금도 발생하는 실측 사례가 있습니다. "어떤 이벤트에서 효과가 있었던 방식"을 다른 이벤트에 그대로 적용하면 성립하지 않는 일이 흔히 발생합니다. 이벤트마다 실측으로 확인해야 합니다.
4. 서브 에이전트(Sub-agent)에는 PreToolUse가 적용되지 않는다
hook의 방어는 Task tool을 경유하는 서브 에이전트에는 적용되지 않습니다 (#34692, #21460). "제약 사항을 서브 에이전트의 프롬프트에 재선언한다"가 정석이지만, 더 엄격한 사실로서, 명시적인 금지 지시 + 경고를 포함했음에도 서브 에이전트가 보호 대상 디렉토리를 삭제하는 데 성공한 실증 사례가 있습니다 (#81782). 텍스트 제약은 확률적으로만 작동합니다. 지키고 싶은 자산은 git으로 관리하여 "방지"가 아닌 "복구 가능성"으로 담보하는 것까지 포함하여 설계해야 합니다.
5. deny-list는 "이름"만 막는다
rm -rf ~를 차단하더라도, 의미적으로 등가한 다른 표기 방식은 그대로 통과합니다. 끝에 리다이렉션을 추가한 것뿐인 rm -rf ~ 2>/dev/null이 텍스트 매칭형 탐지를 통과한 실례가 있습니다 (#60977 등). 금지는 "명령어 이름"이 아니라 "조작 의도"로 설계해야 하며, 그럼에도 완전하지 않을 수 있다는 전제를 가져야 합니다.
설정이 의도와 다르게 해석되는 케이스
6. 허가 설정에 Bash(*)가 있으면 개별 ask가 완전히 무시된다
허가 설정에 Bash(*)가 있으면 개별적인 ask가 완전히 무시됩니다 (#6527). "넓게 허용하면서 위험한 것만 확인"하는 방식은 이 조합에서는 성립하지 않습니다. 확인 단계를 거치고 싶은 것은 PreToolUse hook으로 구현해야 합니다.
7. defaultMode: acceptEdits가 경로별 ask를 조용히 덮어쓴다
특정 경로만 확인하고 싶은 설정이 모드 설정에 조용히 패배합니다 (#63111). 보호는 ask가 아니라 deny로 정의해야 합니다 — 확인 기반의 방어는 설정이 덮어씌워졌을 때 알아차릴 수 없습니다.
8. stdout에 JSON을 출력하면 "Hook Error"가 된다
훅의 출력 대상을 잘못 지정하면 에러로 처리됩니다 (#36121). 메시지는 stderr로 보내고, 스크립트 서두에 INPUT=$(cat)을 작성하는 것이 관례입니다. 사소하지만 처음에 모두가 겪는 실수입니다.
운용 시 알아차리기 어려운 케이스
9. 세션 도중에 추가한 hook은 재시작 전까지 무효
hook 설정의 등록 변경은 세션 재시작 전까지 반영되지 않습니다 (#65953). "추가했는데 실행되지 않는다"는 버그가 아니라 사양(Specification)입니다. 참고로, 이미 등록된 hook의 스크립트 본체 (.ps1/.sh) 수정은 다음 실행 시 즉시 반영되므로, "등록"과 "구현"의 반영 타이밍이 다르다고 기억하는 것이 정확합니다.
10. (Windows) PowerShell 5.1은 BOM 없는 UTF-8의 비 ASCII 문자를 조용히 파괴함
hook 스크립트를 일본어 주석이 포함된 상태로 작성할 경우, **BOM이 포함된 UTF-8 (UTF-8 with BOM)**로 저장하지 않으면 비 ASCII 부분이 조용히 누락됩니다. 에러가 발생하지 않기 때문에 발견이 늦어집니다.
그럼에도 hook을 사용한다 —— 설계 원칙
제한 사항들을 나열했지만, 결론은 "hook을 그만두자"가 아닙니다. 텍스트 지시(CLAUDE.md)가 확률적으로만 작동하는 이상, 기계적 강제성을 띠는 hook은 여전히 가장 강력한 계층입니다. 제한 사항을 포함하여 운용하기 위한 원칙을 정리합니다.
| 원칙 | 내용 |
|---|---|
| deny + exit 0로 통일 | exit 2 기반의 차단은 작성하지 않음 (케이스 1~3) |
| ... |
영어권에서도 "아무도 트윗하지 않는 실패 모드를 포착하는 5가지 hooks", "--no-verify 커밋을 끝내버린 hook", "Stop hook은 대시보드가 아니라 이벤트다"와 같이, 운용 지식의 초점이 "무엇을 작성할 것인가"에서 "어떻게 끝까지 지켜낼 것인가"로 이동하고 있습니다.
요약
- hook의 방어는 만능이 아니다: exit 2의 불발 (도구별·OS별·이벤트별), 서브 에이전트(Sub-agent) 통과, deny-list의 의미적 회피 —— "지키고 있다고 착각하는" 허점들은 구체적으로 열거할 수 있습니다.
- 대책의 공통점은 3가지: deny 기반으로 통일 / 도입 전에 위반 사례를 실측 / 마지막은 git의 복구 가능성
- 그럼에도 텍스트 지시보다 확실한 계층이라는 사실은 변하지 않습니다. 제한 사항을 인지한 상태에서 사용한다면, hook은 오늘도 최강의 문지기입니다.
"이 케이스도 지키지 못했다"라는 추가 사례가 있다면, 꼭 댓글로 알려주세요. 제한 사항 모음은 망라될수록 가치가 높아집니다.
필자는 Windows 환경에서 15개의 hook을 상시 운용 중입니다. 참조한 Issue는 AI 코딩 도구의 보고를 6개 소스에서 자동 수집하는 개인 파이프라인의 수집분과 공식 리포지토리에서 가져왔습니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기