Claude Code hooks는 단순한 “명령 실행 장치"가 아니라, 에이전트 실행 흐름을 정책화하는 인터페이스에 가깝습니다. 핵심은 언제(이벤트), 무엇을(명령/프롬프트), 어떻게(종료 코드/JSON 출력)를 명시적으로 분리해 제어하는 데 있습니다.
Sources
1) 설정 모델의 핵심: Event -> matcher -> hooks
문서의 기본 구조는 hooks.EventName[] 아래에 matcher와 hook 배열을 두는 형태입니다. 특히 PreToolUse, PermissionRequest, PostToolUse는 matcher 기반 도구 필터링이 가능하고, UserPromptSubmit, Stop, SubagentStop 같은 이벤트는 matcher 없이도 구성할 수 있습니다.
즉, 운영 관점에서는 다음처럼 생각하면 명확합니다.
- 이벤트는 “실행 시점"을 정의한다.
- matcher는 “대상 도구 범위"를 정의한다.
- hooks 배열은 “실행 액션 집합"을 정의한다.
flowchart LR
A["Hook 이벤트 발생"] --> B["matcher 평가"]
B --> C{"일치 여부"}
C -->|"일치"| D["hooks 배열 병렬 실행"]
C -->|"불일치"| E["건너뜀"]
D --> F["종료 코드 또는 JSON 출력 처리"]
classDef input fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
classDef process fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
classDef decision fill:#fff8e1,stroke:#ff8f00,color:#e65100
classDef output fill:#fff3e0,stroke:#ef6c00,color:#e65100
class A input
class B,D process
class C decision
class E,F output문서에서 명시하듯 matcher는 대소문자를 구분하고, 정규식(Edit|Write)과 와일드카드(*)를 지원합니다. 또한 프로젝트 경로 기반 스크립트를 쓸 때는 CLAUDE_PROJECT_DIR 환경 변수를 사용하는 패턴이 권장됩니다.
2) 이벤트 수명주기와 입출력 계약
Hook 입력은 공통 필드(session_id, transcript_path, cwd, permission_mode)와 이벤트별 필드로 구성됩니다. 이 구조 덕분에 같은 스크립트에서도 이벤트 문맥에 따라 분기 처리가 가능합니다.
특히 실무에서 자주 쓰는 이벤트는 다음 흐름으로 이해하면 좋습니다.
PreToolUse: 도구 실행 직전 정책 검사PermissionRequest: 권한 대화 자동 허용/거부 판단PostToolUse: 실행 직후 피드백/추가 컨텍스트 주입UserPromptSubmit: 프롬프트 전처리 또는 차단Stop/SubagentStop: 종료 허용 여부 재평가
flowchart TD A["UserPromptSubmit"] --> B["PreToolUse"] B --> C["PermissionRequest"] C --> D["도구 실행"] D --> E["PostToolUse"] E --> F["Stop/SubagentStop"] classDef stage fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20 classDef action fill:#e3f2fd,stroke:#1565c0,color:#0d47a1 class A,B,C,E,F stage class D action
또한 문서에는 SessionStart/SessionEnd 이벤트도 제공되며, SessionStart에서는 CLAUDE_ENV_FILE을 통해 이후 Bash 실행에 사용할 환경 변수를 유지할 수 있다고 설명합니다. 세션 초기화 자동화가 필요한 팀이라면 매우 중요한 포인트입니다.
3) 결정 제어의 실전 포인트: 종료 코드 vs JSON 출력
레퍼런스가 강조하는 포인트는 “단순 종료 코드"와 “구조화된 JSON 출력"의 역할 분리입니다.
- 종료 코드
0: 성공 (일부 이벤트는 stdout이 컨텍스트로 주입됨) - 종료 코드
2: 차단성 오류 (hook 이벤트별로 차단 동작) - 그 외 코드: 비차단 오류 (verbose 모드에서 stderr 확인)
그리고 JSON 출력은 종료 코드 0에서만 처리된다는 제약이 핵심입니다. 예를 들어 PreToolUse는 permissionDecision(allow/deny/ask)과 updatedInput으로 실행 전 입력을 정책적으로 수정할 수 있습니다.
flowchart LR
A["Hook 실행 결과"] --> B{"exit code"}
B -->|"0"| C["stdout JSON 파싱 가능"]
B -->|"2"| D["차단 오류 처리"]
B -->|"기타"| E["비차단 오류 로그"]
C --> F{"hookSpecificOutput 존재?"}
F -->|"예"| G["결정 제어 적용"]
F -->|"아니오"| H["기본 흐름 유지"]
classDef base fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
classDef decision fill:#fff8e1,stroke:#ff8f00,color:#e65100
classDef caution fill:#ffebee,stroke:#c62828,color:#b71c1c
class A,C,G,H base
class B,F decision
class D,E caution추가로 문서에는 type: "prompt" 기반 hook을 소개하며, LLM이 { "ok": true|false, "reason": "..." } 형태로 판단을 반환하도록 정의합니다. 다만 성능 관점에서 bash 명령형 hook보다 느릴 수 있으므로, 복잡한 맥락 판단에만 선택적으로 배치하는 것이 합리적입니다.
4) 보안과 운영: “강력한 기능"일수록 방어선을 먼저 세우기
문서는 hooks가 임의 셸 명령을 자동 실행한다는 점을 명확히 경고합니다. 따라서 실무에서는 다음 방어 원칙을 기본값으로 두는 것이 안전합니다.
- 입력 검증/정규화(특히 경로, 사용자 입력, JSON 파싱 실패 처리)
- 셸 변수 인용(
"$VAR") - 경로 순회(
..) 차단 - 절대 경로 사용 또는
CLAUDE_PROJECT_DIR기반 경로 고정 - 민감 파일(
.env,.git/, 키 파일) 제외 정책
문서의 실행 세부에서도 여러 hook이 병렬 실행되고 동일 명령은 중복 제거된다고 안내합니다. 따라서 hook 스크립트는 멱등성(idempotency) 과 동시 실행 안정성을 전제로 작성해야 예측 가능한 운영이 가능합니다.
Practical Takeaways
- 첫 도입은
PreToolUse+PostToolUse중심의 단순 규칙부터 시작하고,Stop/SubagentStop의 차단 정책은 점진적으로 강화하세요. PreToolUse에서updatedInput을 쓸 경우, 변경 전후 값을 로그에 남겨 추적 가능성을 확보하세요.prompt기반 hook은 “정적 규칙으로 표현하기 어려운 판단"에만 쓰고, 나머지는 bash hook으로 유지해 지연을 최소화하세요.SessionStart에서CLAUDE_ENV_FILE을 사용한다면 환경 변수 누적 정책(덮어쓰기/추가)을 팀 규칙으로 명시하세요.- 운영 전에는
claude --debug기반으로 matcher 일치, 종료 코드 처리, stderr 메시지 품질을 반드시 리허설하세요.
Conclusion
Claude Code hooks의 본질은 “자동화” 자체가 아니라 “실행 경계의 명시적 제어"에 있습니다. 이벤트 구조, 출력 계약(종료 코드/JSON), 보안 원칙을 함께 설계하면, 단순한 편의 기능을 넘어 팀 단위의 안정적인 에이전트 운영 레이어로 확장할 수 있습니다.