← 모든 글

Claude Code 훅 완벽 해설: 에이전트를 감싸는 결정론적 계층

가이드에서: Claude Code Comprehensive Guide

Claude Code 훅이란 무엇인가요? 훅은 Claude Code가 라이프사이클의 정해진 시점 — 도구 호출 직전, 편집 직후, 세션 시작 시, Claude가 응답을 마칠 때 — 에 자동으로 실행하는 사용자 정의 셸 명령(그리고 HTTP 엔드포인트, MCP 도구, 모델 프롬프트)입니다.1 CLAUDE.md가 모델이 아마도 따를 지시를 전달하는 반면, 훅은 모델의 협조 여부와 무관하게 실행됩니다. 아무 세션에서나 /hooks를 입력하면 모든 라이프사이클 이벤트와 각 이벤트에 연결된 항목을 확인할 수 있습니다. {.answer-block}

대부분의 개발자는 두 가지 제어 계층으로 Claude Code를 운용합니다. 에이전트가 무엇을 해도 되는지를 통제하는 권한, 그리고 무엇을 해야 하는지를 서술하는 CLAUDE.md입니다. 훅은 세 번째 계층이자, 무언가를 보장하는 유일한 계층입니다. 아래에서 멘탈 모델, 현재 문서에 있는 모든 라이프사이클 이벤트, 정확한 입력/출력 계약, 설정 방법, 다섯 가지 실전 패턴, 그리고 선택 기준 프레임워크를 다룹니다. 모든 API 세부 사항은 2026년 7월 1일 기준 공식 훅 레퍼런스와 가이드로 검증했습니다. 이 시스템은 빠르게 변하므로, 이 글과 레퍼런스가 다를 때는 레퍼런스가 우선합니다. (Claude Code가 처음이라면 5분 설정이나 Claude Code 입문 경로부터 시작하세요.)

TL;DR: 훅은 stdin으로 JSON을 받고 종료 코드 또는 stdout의 JSON으로 응답합니다. exit 0은 허용, exit 2는 차단(차단이 가능한 이벤트에서)이며, 관례적인 Unix 실패 코드인 exit 1은 아무것도 차단하지 않습니다 — 이것이 훅에서 가장 큰 함정입니다.2 PreToolUse, Stop 같은 이벤트 이름 아래 settings.json에 설정하고 매처(matcher)로 필터링합니다. 반드시 항상 일어나야 하는 일에는 훅을, 모델이 알고만 있으면 되는 것에는 CLAUDE.md를 사용하세요.

멘탈 모델: 비결정론적 코어를 감싸는 보장

코딩 에이전트는 확률적 시스템입니다. 편집할 때마다 Prettier를 실행하라고 하면 실제로 실행합니다 — 대부분의 경우에는요. 변경이 사소해 보일 때, 컨텍스트가 길어질 때, 지시 문구가 다르게 해석될 때는 그 단계를 건너뛸 수 있습니다. CLAUDE.md, 스킬, 프롬프트는 모두 제안입니다. 품질이 높고 대체로 지켜지지만, 결코 보장되지는 않습니다.

훅은 그 코어를 감싸는 결정론적 껍데기입니다. 공식 정의는 이렇습니다. “Claude Code 라이프사이클의 특정 시점에 자동으로 실행되는 사용자 정의 셸 명령, HTTP 엔드포인트 또는 LLM 프롬프트”로서, “LLM이 실행 여부를 선택하도록 맡기는 대신 특정 동작이 항상 일어나도록 보장하는, Claude Code 동작에 대한 결정론적 제어”를 제공합니다.3 포매터는 모든 편집마다 실행됩니다. 명령 가드는 모든 Bash 호출을 평가합니다. 완료 게이트는 모든 종료를 검사합니다.

이 강제력은 겉치레가 아니라 실제입니다. PreToolUse 훅은 어떤 권한 모드 검사보다도 먼저 실행되므로, permissionDecision: "deny"를 반환하는 훅은 bypassPermissions 모드나 --dangerously-skip-permissions 아래에서도 도구를 차단합니다. 그 반대는 성립하지 않습니다. "allow"를 반환하는 훅이 설정의 deny 규칙을 완화할 수는 없습니다. 훅은 권한이 허용하는 것보다 정책을 더 조일 수는 있어도, 결코 느슨하게 만들 수는 없습니다.4

라이프사이클: 모든 훅 이벤트

2026년 7월 1일 기준으로 레퍼런스에는 30개의 훅 이벤트가 문서화되어 있습니다.1 이들은 세 가지 주기로 나뉩니다. 세션당 한 번(SessionStart, SessionEnd), 턴당 한 번(UserPromptSubmit, Stop, StopFailure), 그리고 에이전틱 루프 안의 모든 도구 호출마다(PreToolUse, PostToolUse)입니다. 나머지는 설정 변경, 컴팩션, 서브에이전트, MCP 상호작용 같은 특정 조건에서 실행됩니다.

이벤트 실행 시점 실제 활용 예
SessionStart 세션이 시작되거나 재개될 때 git 브랜치와 열린 이슈를 컨텍스트로 주입
Setup --init-only, 또는 -p 모드의 --init/--maintenance 에이전트 실행 전 CI에서 의존성 설치
UserPromptSubmit 프롬프트를 제출한 뒤, Claude가 처리하기 전 현재 날짜 덧붙이기; 시크릿이 포함된 프롬프트 거부
UserPromptExpansion 입력한 커맨드가 프롬프트로 확장될 때 스킬/커맨드 확장을 감사하거나 거부
PreToolUse 도구 호출이 실행되기 전 파괴적인 셸 명령 차단
PermissionRequest 권한 대화 상자가 나타날 때 신뢰하는 명령을 자동 승인해 확인 절차 생략
PermissionDenied 자동 모드 분류기가 도구 호출을 거부할 때 retry: true를 반환해 모델이 재시도할 수 있게 함
PostToolUse 도구 호출이 성공한 후 편집된 모든 파일 자동 포맷
PostToolUseFailure 도구 호출이 실패한 후 실패한 명령을 로깅해 분류
PostToolBatch 병렬 도구 호출 배치 이후, 다음 모델 호출 전 에이전틱 루프 체크포인트 저장 또는 중단
Notification Claude Code가 알림을 보낼 때 Claude가 입력을 기다릴 때 데스크톱 알림
MessageDisplay 어시스턴트 메시지 텍스트가 표시되는 동안 화면 표시 내용 마스킹(표시 전용; 트랜스크립트는 그대로)
SubagentStart 서브에이전트가 생성될 때 에이전트 유형별 컨텍스트 주입
SubagentStop 서브에이전트가 종료될 때 반환되기 전에 서브에이전트 출력 검증
TaskCreated TaskCreate로 작업이 생성될 때 작업 이름 또는 범위 규칙 강제
TaskCompleted 작업이 완료로 표시될 때 완료가 확정되기 전에 인수 기준 검증
Stop Claude가 응답을 마칠 때 완료 게이트: 테스트가 통과할 때까지 종료 차단
StopFailure API 오류로 턴이 끝날 때 rate_limit이나 billing_error 발생 시 알림(로그 전용; 출력 무시)
TeammateIdle 에이전트 팀의 팀메이트가 유휴 상태로 전환되기 직전 팀메이트가 큐를 계속 처리하며 일하도록 유지
InstructionsLoaded CLAUDE.md 또는 .claude/rules/*.md 파일이 컨텍스트에 로드될 때 어떤 지침이 세션에 들어왔는지 로깅
ConfigChange 세션 중에 설정 파일이 변경될 때 승인되지 않은 설정 편집 차단
CwdChanged 작업 디렉터리가 변경될 때 direnv 방식 환경 다시 로드
FileChanged 감시 중인 파일이 디스크에서 변경될 때 .env 변경 시 환경 변수 새로 고침
WorktreeCreate --worktree 또는 isolation: "worktree"로 워크트리가 생성될 때 기본 git 워크트리 프로비저닝 대체
WorktreeRemove 워크트리가 제거될 때 세션 또는 서브에이전트 종료 시 커스텀 정리
PreCompact 컨텍스트 컴팩션 전 잃어서는 안 되는 상태 저장
PostCompact 컴팩션 완료 후 핵심 컨텍스트 재주입
Elicitation MCP 서버가 사용자 입력을 요청할 때 헤드리스 실행에서 폼 자동 입력
ElicitationResult MCP elicitation에 응답한 후 반환되기 전에 응답 검증 또는 재정의
SessionEnd 세션이 종료될 때 로그 보관, 리소스 정리

이 중 대부분은 필요하지 않을 것입니다. 거의 모든 프로덕션 구성은 다섯 가지 — PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop — 로 이루어집니다. 나머지는 언젠가 필요해지는 날을 위해 존재합니다.

계약: JSON 입력, 종료 코드 또는 JSON 출력

커맨드 훅은 stdin으로 JSON을 받고 종료 코드, stdout, stderr로 응답합니다. (HTTP 훅은 같은 JSON을 POST 본문으로 받고 응답 본문으로 답합니다.)2

모든 이벤트는 공통 엔벨로프 — session_id, transcript_path, cwd, hook_event_name, 그리고 대부분의 이벤트에서 permission_mode — 에 이벤트별 필드를 더해 전달합니다. Bash 명령에 대한 PreToolUse 훅은 다음을 받습니다:

{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
  "cwd": "/home/user/my-project",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "npm test" }
}

다른 이벤트는 뒷부분이 달라집니다. UserPromptSubmitprompt를, SessionStartsource(startup/resume/clear/compact)를, Stopstop_hook_activelast_assistant_message를 담습니다. 서브에이전트 안에서 실행되는 훅은 추가로 agent_idagent_type을 받습니다.2

종료 코드

결과는 세 가지입니다.2

  • Exit 0 — 성공. Claude Code는 stdout에서 JSON 출력 필드를 파싱합니다. 대부분의 이벤트에서 stdout은 디버그 로그로만 가지만, UserPromptSubmit, UserPromptExpansion, SessionStart에서는 일반 stdout이 Claude가 볼 수 있는 컨텍스트로 추가됩니다.
  • Exit 2 — 차단 오류. stdout(JSON 포함)은 무시되고, stderr가 오류 메시지로 Claude에게 전달됩니다. “차단”이 무엇을 의미하는지는 이벤트에 따라 다릅니다.
  • 그 외의 종료 코드 — 비차단 오류. 트랜스크립트에 <hook name> hook error 알림이 표시되고 실행은 계속됩니다.

마지막 줄은 굵게 강조할 만합니다. exit 1은 아무것도 차단하지 않습니다. 문서도 이를 직접 경고합니다. 1이 관례적인 Unix 실패 코드임에도, Claude Code는 exit 1을 비차단 오류로 취급하고 그대로 진행합니다. 정책 훅은 반드시 exit 2를 사용해야 합니다.2

이벤트별 exit 2의 효과는 다음과 같습니다.2

이벤트 Exit 2 효과
PreToolUse 도구 호출 차단
PermissionRequest 권한 거부
UserPromptSubmit 처리를 차단하고 프롬프트 삭제
UserPromptExpansion 확장 차단
Stop / SubagentStop 종료를 막고 대화 계속
TeammateIdle 팀메이트의 유휴 전환 방지
TaskCreated / TaskCompleted 생성 롤백 / 완료 방지
ConfigChange 설정 변경 차단(policy_settings 제외)
PreCompact 컴팩션 차단
PostToolBatch 다음 모델 호출 전에 에이전틱 루프 중단
Elicitation / ElicitationResult elicitation 거부 / 응답을 거절로 전환
WorktreeCreate 0이 아닌 모든 종료 코드가 워크트리 생성을 중단

그 밖의 이벤트는 차단할 수 없습니다. PostToolUsePostToolUseFailure는 stderr를 Claude에게 보여 주며(도구는 이미 실행됨), SessionStart, Notification, SessionEnd, CwdChanged, FileChanged, PostCompact, SubagentStart, Setup은 stderr를 사용자에게만 보여 줍니다. StopFailure, InstructionsLoaded, MessageDisplay, PermissionDenied는 종료 코드를 무시합니다 — PermissionDenied에서 유일한 수단은 JSON retry: true입니다.2

JSON 출력

차단 아니면 침묵보다 세밀한 제어가 필요하면, exit 0으로 종료하면서 stdout에 JSON 객체를 출력하세요. 먼저 규칙 하나부터: 종료 코드 아니면 JSON, 둘 다는 절대 안 됩니다. JSON은 exit 0에서만 처리되고, exit 2는 JSON을 버립니다.5

범용 필드는 모든 이벤트에서 동작합니다. continue: false는 Claude를 완전히 중단시키고(stopReason이 사용자에게 표시됨), suppressOutput은 stdout을 트랜스크립트에서 숨기며, systemMessage는 사용자에게 경고를 표시하고, terminalSequence는 허용 목록에 있는 터미널 이스케이프(데스크톱 알림, 창 제목, 벨)를 내보냅니다. 결정 필드는 이벤트별로 다릅니다.5

이벤트 결정 패턴 주요 필드
UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact 최상위 decision decision: "block" + reason(Claude에게 표시). 허용하려면 decision 생략
PreToolUse hookSpecificOutput permissionDecision: "allow" | "deny" | "ask" | "defer", 추가로 permissionDecisionReason, 그리고 실행 전에 도구 인수를 재작성하는 updatedInput
PermissionRequest hookSpecificOutput decision.behavior: "allow" | "deny", 선택적으로 decision.updatedInput
PermissionDenied hookSpecificOutput retry: true는 모델에게 재시도해도 된다고 알림
PostToolUse hookSpecificOutput updatedToolOutput이 도구 결과를 대체
Stop / SubagentStop hookSpecificOutput additionalContext: 훅 오류로 집계되지 않으면서 대화를 이어 가는 비오류 피드백
SessionStart, Setup, SubagentStart 컨텍스트 전용 additionalContext, 그리고 SessionStart 전용 initialUserMessage, sessionTitle, watchPaths, reloadSkills. 차단 불가
MessageDisplay hookSpecificOutput displayContent는 화면 표시 텍스트만 대체
Elicitation / ElicitationResult hookSpecificOutput action: "accept" | "decline" | "cancel", 그리고 content
WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged 없음 부수 효과 전용

사람들이 자주 걸리는 세부 사항이 두 가지 있습니다. 첫째, PreToolUse는 최상위 decision 패턴의 예외입니다. 과거에는 최상위 decision/reason을 사용했지만 이 이벤트에서는 폐기(deprecated)되었으며("approve"/"block""allow"/"deny"로 매핑됨), hookSpecificOutput.permissionDecision을 사용해야 합니다.5 둘째, 여러 PreToolUse 훅의 판단이 엇갈리면 우선순위는 deny > defer > ask > allow입니다.5

설정: settings.json, 매처, 범위

훅 설정은 세 단계로 중첩됩니다. 이벤트를 고르고, 실행 시점을 필터링할 매처 그룹을 추가한 뒤, 실행할 훅 핸들러를 하나 이상 정의합니다.6

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "/path/to/lint-check.sh" }
        ]
      }
    ]
  }
}

이 설정을 어디에 두느냐가 범위를 결정합니다. ~/.claude/settings.json은 모든 프로젝트에 적용되고, .claude/settings.json은 프로젝트 범위이며 커밋할 수 있고, .claude/settings.local.json은 프로젝트 범위이지만 gitignore 대상입니다. 표준 설정 우선순위가 그대로 적용됩니다 — 관리형 정책이 로컬보다, 로컬이 프로젝트보다, 프로젝트가 사용자 설정보다 우선합니다.9 훅은 플러그인(hooks/hooks.json)과 스킬 또는 에이전트 프런트매터로도 배포할 수 있으며, 엔터프라이즈 관리자는 사용자가 재정의할 수 없는 관리형 훅을 강제할 수 있습니다.6

매처는 포함된 문자에 따라 평가됩니다. "*", "", 또는 매처 생략은 모든 것과 일치합니다. 문자, 숫자, _, -, 공백, 쉼표, |만으로 이루어진 값은 정확한 문자열 또는 목록입니다(Bash, Edit|Write). 그 외의 값은 앵커 없는 JavaScript 정규식이 되므로 Edit.*EditNotebookEdit 둘 다와 일치합니다 — 정확히 하나의 도구만 의도한다면 ^Edit$처럼 앵커를 붙이세요. 매처는 대소문자를 구분하며, 각 이벤트는 자체 필드로 매칭합니다. 도구 이벤트는 도구 이름, SessionStartsource, SubagentStart는 에이전트 유형, Notification은 알림 유형입니다.6 도구 이벤트에서 더 정밀한 필터링이 필요하면 핸들러별 if 필드가 "Bash(git *)" 같은 권한 규칙 하나를 받습니다 — 다만 이는 최선 노력(best-effort) 방식이어서 파싱할 수 없는 명령에서는 열린 채로 통과(fail open)하므로, 확실한 보장이 필요하면 if가 아니라 권한 규칙을 사용하세요.6

핸들러는 다섯 유형입니다. command(셸), http(POST 엔드포인트), mcp_tool, prompt(단일 턴 모델 평가), 그리고 agent(Read/Grep/Glob 접근 권한을 가진 서브에이전트; 실험적)입니다. 기본 타임아웃은 command/http/mcp_tool이 600초(UserPromptSubmit은 30초, MessageDisplay는 10초로 단축), prompt가 30초, agent가 60초이며, 훅별로 timeout을 지정해 재정의할 수 있습니다.6 매칭된 모든 훅은 병렬로 실행되고 동일한 핸들러는 중복 제거되며, $CLAUDE_PROJECT_DIR는 스크립트가 프로젝트 루트를 가리키도록 해 줍니다.

/hooks로 확인하세요. 모든 이벤트, 각 이벤트에 설정된 훅, 그리고 각 훅이 어느 설정 파일에서 왔는지 보여 주는 읽기 전용 브라우저입니다. 무언가를 바꾸려면 JSON을 직접 편집하거나 Claude에게 요청하세요. 모든 훅을 일시적으로 끄려면 "disableAllHooks": true를 설정하세요.6

다섯 가지 패턴

일반화한 최소 버전입니다. 훅 튜토리얼은 이 중 몇 가지의 더 완전한 프로덕션 버전을 만들고, Apple 개발을 위한 훅은 이를 iOS 툴체인에 적용합니다.

1. 편집 후 자동 포맷 (PostToolUse)

공식 가이드 그대로입니다. Claude가 건드리는 모든 파일이 예외 없이 포맷됩니다.3

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
        ]
      }
    ]
  }
}

스택에 따라 명령을 ruff format, gofmt, swiftformat으로 바꾸면 됩니다.

2. 위험한 명령 차단 (PreToolUse, exit 2)

#!/bin/bash
# .claude/hooks/guard-bash.sh — register on PreToolUse, matcher "Bash"
command=$(jq -r '.tool_input.command // empty')
case "$command" in
  *"rm -rf"* | *"git push --force"* | *"DROP TABLE"*)
    echo "Blocked: matches a destructive pattern. Propose a safer alternative." >&2
    exit 2 ;;
esac
exit 0

Exit 2는 호출을 차단하고 stderr를 Claude에게 전달하므로, Claude는 맹목적으로 재시도하는 대신 방향을 조정합니다. JSON 버전인 permissionDecision: "deny"와 사유를 함께 쓰면 같은 효과를 내면서, "ask"(사람에게 에스컬레이션)나 updatedInput(명령 재작성)으로 확장할 여지도 생깁니다.5

3. 세션 시작 시 컨텍스트 주입 (SessionStart)

SessionStart 훅의 일반 stdout은 Claude가 볼 수 있는 컨텍스트가 됩니다. JSON도 필요 없습니다.1

#!/bin/bash
# .claude/hooks/session-context.sh — register on SessionStart
echo "Current branch: $(git branch --show-current)"
echo "Recent commits:"
git log --oneline -5
echo "Uncommitted files: $(git status --porcelain | wc -l | tr -d ' ')"
exit 0

이 패턴은 동적 상태에 사용하세요. 정적인 규칙은 CLAUDE.md에 두는 것이 맞습니다. 스크립트가 필요 없는 컨텍스트에는 공식 문서 역시 CLAUDE.md를 권장합니다.1

4. Stop의 완료 게이트

Stop은 Claude가 응답을 마칠 때 실행됩니다. 이를 차단하면 조건이 충족될 때까지 에이전트가 계속 일하게 만들 수 있습니다:

#!/bin/bash
# .claude/hooks/stop-gate.sh — register on Stop
input=$(cat)
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0  # already continuing because of this hook; don't loop forever
fi
if ! npm test --silent > /tmp/stop-gate.log 2>&1; then
  jq -n '{decision: "block", reason: "Tests are failing. Fix them before finishing. Log: /tmp/stop-gate.log"}'
fi
exit 0

stop_hook_active 검사는 중요합니다. Claude Code는 Stop 훅의 연속 차단을 8회로 강제 제한하는데, 자신이 이미 계속 진행을 유발했는지 확인하지 않는 게이트는 그 한도를 순식간에 소진합니다.7 더 부드럽게 유도하려면 decision: "block" 대신 hookSpecificOutput.additionalContext를 반환하세요. 똑같이 대화가 이어지지만, 훅 오류가 아니라 라벨이 붙은 피드백으로 처리됩니다. 일회성 조건이라면 내장 /goal 커맨드가 설정 없이 쓸 수 있는 세션 범위의 프롬프트 기반 Stop 훅입니다.1

5. 디스패처: 하나의 진입점, 여러 개의 작은 훅

훅 열 개를 등록하면 settings.json 항목 열 개가 생기고, 이 항목들은 머신과 프로젝트 사이에서 서서히 어긋납니다. 대안은 이벤트마다 디스패처 하나만 등록하고 규약에 따라 라우팅하는 것입니다.

#!/bin/bash
# .claude/hooks/dispatch.sh — register once per event you care about
input=$(cat)
event=$(echo "$input" | jq -r '.hook_event_name')
dir="$CLAUDE_PROJECT_DIR/.claude/hooks/$event"
[ -d "$dir" ] || exit 0
for hook in "$dir"/*.sh; do
  [ -x "$hook" ] || continue
  echo "$input" | "$hook" || exit $?
done
exit 0

이제 가드를 추가하는 일은 .claude/hooks/PreToolUse/에 새 파일을 만들고 chmod +x를 실행하는 것으로 끝납니다. settings.json은 전혀 바뀌지 않고, 각 스크립트는 따로 테스트할 수 있을 만큼 작게 유지되며, 첫 번째 exit 2가 그대로 전파됩니다. 한 가지 주의점이 있습니다. 디스패처는 Claude Code가 병렬로 실행할 것을 직렬화하며, 종료 코드 방식의 훅에 가장 잘 맞습니다. stdout에는 정확히 하나의 JSON 객체만 있어야 하므로, JSON을 내보내는 훅은 독립적으로 두는 것이 좋습니다.5

훅 vs CLAUDE.md vs 스킬 vs 메모리

네 가지 메커니즘, 네 가지 역할:

메커니즘 역할 선택 기준
강제 건너뛰는 것이 불가능해야 한다면 — 포맷팅, 안전장치, 게이트 — 훅입니다
CLAUDE.md 지침 모델이 매 세션 알고 있어야 하는 규약이라면 — 스택, 스타일, 명령 — CLAUDE.md입니다
스킬 능력 자체 지침과 스크립트를 갖추고 필요할 때 호출되는 절차라면 스킬입니다
메모리 기억 한 세션에서 배운 사실을 이후 세션이 필요로 한다면 메모리입니다

실패 양상은 양방향으로 나타납니다. 규약을 훅으로 코드화하면, 지침 한 문장이면 충분히 처리될 일을 강제하는 깨지기 쉬운 스크립트만 얻게 됩니다. 정책을 CLAUDE.md 산문으로 적어 두면, 하필 중요한 그날 main에 강제 푸시하는 에이전트를 얻게 됩니다. 판별법은 이렇습니다. 모델이 이것을 한 번 무시했을 때의 비용은 얼마인가? 짜증 정도라면 → CLAUDE.md. 사고라면 → 훅.

훅이 할 수 없는 것

솔직한 한계 목록이며, 전부 공식 문서에 있는 내용입니다.7

  • 훅은 도구나 슬래시 커맨드를 호출할 수 없습니다. 커맨드 훅이 말할 수 있는 것은 stdout, stderr, 종료 코드뿐입니다. additionalContext로 반환한 컨텍스트는 일반 텍스트로 주입됩니다.
  • PostToolUse는 되돌릴 수 없습니다. 도구는 이미 실행되었습니다. 예방은 PreToolUse의 몫입니다.
  • Stop은 모든 응답 종료 시 실행됩니다. “작업 완료” 시점만이 아니며, 사용자 인터럽트에서는 절대 실행되지 않습니다(API 오류는 대신 StopFailure를 발생시킵니다). 게이트 로직은 작업 중간의 종료도 감안해야 합니다.
  • PermissionRequest는 헤드리스(-p) 모드에서 실행되지 않습니다. 자동화된 권한 결정에는 PreToolUse를 사용하세요.
  • PreToolUse@로 참조된 파일을 보지 못합니다. 프롬프트에서 @로 끌어온 파일에는 도구 호출이 개입하지 않습니다. 그 경로로부터 파일을 보호하려면 Read deny 규칙을 사용하세요.1
  • 병렬 updatedInput은 비결정적입니다. 여러 PreToolUse 훅이 같은 도구의 인수를 재작성하면 마지막에 끝난 훅이 이깁니다. 각 재작성은 훅 하나가 전담하게 하세요.
  • 타임아웃은 훅을 취소합니다. 커맨드 훅의 기본값은 600초(UserPromptSubmit은 30초, MessageDisplay는 10초)입니다. 타임아웃되는 느린 게이트는 실행되지 않은 게이트와 같습니다.
  • 출력은 10,000자로 제한됩니다. 초과분은 파일에 기록되고 미리보기로 대체됩니다.
  • 훅은 사용자의 전체 권한으로 실행됩니다. 레퍼런스 자체의 경고를 옮기면, 훅은 “사용자 계정이 접근할 수 있는 모든 파일을 수정, 삭제, 접근할 수 있습니다. 설정에 추가하기 전에 모든 훅 명령을 검토하고 테스트하세요.”8 변수는 따옴표로 감싸고, 절대 경로를 사용하고, 민감한 파일은 건드리지 마세요.
  • 고장 난 훅은 고칠 때까지 모든 세션을 저하시킵니다. 트랜스크립트 뷰(Ctrl+O), claude --debug-file /tmp/claude.log, 또는 세션 중 /debug로 디버깅하세요. 고전적인 함정은 시작할 때 echo를 출력해 훅의 JSON 출력을 망가뜨리는 셸 프로파일입니다.7

FAQ

Claude Code 훅이란 무엇인가요?

훅은 Claude Code가 라이프사이클의 특정 시점에 자동으로 실행하는 사용자 정의 명령 — 셸 스크립트, HTTP 엔드포인트, MCP 도구, 모델 프롬프트 — 입니다.3 stdin으로 이벤트 JSON을 받고 종료 코드 또는 JSON으로 응답합니다. 도구 호출을 차단하고, 컨텍스트를 주입하고, 인수를 재작성하고, 에이전트가 계속 일하게 만듭니다. CLAUDE.md 지침과 달리 모델의 행동과 무관하게 매번 실행됩니다.

PreToolUse 훅과 권한의 차이는 무엇인가요?

권한 규칙은 선언적입니다. Claude Code가 스스로 평가하는 정적인 allow/deny/ask 패턴입니다. PreToolUse 훅은 프로그래밍이 가능합니다. 여러분의 코드가 전체 도구 입력을 검사하고 결정합니다. 훅은 권한 모드 검사보다 먼저 실행되므로 훅의 "deny"bypassPermissions 모드에서도 유지됩니다. 반면 훅의 "allow"는 설정의 deny 규칙을 재정의할 수 없습니다.4 패턴으로 표현할 수 있는 것에는 권한 규칙을 사용하고, 결정에 로직, 외부 상태, 입력 재작성이 필요할 때 훅을 꺼내 드세요.

훅은 헤드리스(-p) 모드에서도 동작하나요?

네 — 문서화된 예외가 하나 있습니다. PermissionRequest 훅은 비대화형 모드에서 실행되지 않으므로, 자동화된 권한 결정은 PreToolUse에 두어야 합니다.7 헤드리스 모드는 대화형 세션이 무시하는 옵션 하나도 열어 줍니다. permissionDecision: "defer"는 도구 호출을 일시 정지시켜, 이를 감싸는 프로세스(Agent SDK 앱, 커스텀 UI)가 입력을 수집한 뒤 나중에 세션을 재개할 수 있게 합니다.5

훅이 실행은 되는데 왜 아무것도 차단하지 않나요?

거의 항상 계약 위반이 원인입니다. exit 1은 차단하지 않습니다. exit 2만 차단하며, 그것도 차단을 지원하는 이벤트에서만입니다.2 JSON 결정은 exit 0에서만 파싱됩니다. {"decision": "block"}을 출력하고 exit 2로 종료하는 스크립트는 JSON이 버려집니다. 그리고 매처는 대소문자를 구분합니다. bash는 절대 Bash와 일치하지 않습니다. /hooks로 등록 상태를 확인한 다음, 샘플 JSON을 스크립트에 파이프로 넣고 echo $?를 확인해 테스트하세요.7

출처

2026년 7월 1일에 공식 문서를 기준으로 검증했습니다. 훅 API는 Claude Code v2.1.x 릴리스를 거치며 크게 변해 왔으므로(새 이벤트, 새 필드, 매처 의미론), 버전에 민감한 세부 사항은 “이 날짜 기준”으로 받아들이세요.

이 사이트의 관련 글: 프롬프트 훅과 에이전트 훅까지 포함한 전체 시스템 관점은 Claude Code 가이드의 훅 섹션, 완전한 설정과 함께 다섯 가지 프로덕션 빌드를 다루는 훅 튜토리얼, iOS에 적용한 패턴은 Apple 개발을 위한 훅, 아직 Claude Code를 설치하지 않았다면 퀵스타트를 참고하세요.


  1. Anthropic, “Hooks reference — Hook lifecycle and hook events.” code.claude.com/docs/en/hooks#hook-events 

  2. Anthropic, “Hooks reference — Hook input and output; exit code output; exit code 2 behavior per event.” code.claude.com/docs/en/hooks#exit-code-output 

  3. Anthropic, “Automate actions with hooks.” code.claude.com/docs/en/hooks-guide 

  4. Anthropic, “Hooks guide — Hooks and permission modes.” code.claude.com/docs/en/hooks-guide#hooks-and-permission-modes 

  5. Anthropic, “Hooks reference — JSON output and decision control.” code.claude.com/docs/en/hooks#json-output 

  6. Anthropic, “Hooks reference — Configuration: hook locations, matcher patterns, hook handler fields, the /hooks menu.” code.claude.com/docs/en/hooks#configuration 

  7. Anthropic, “Hooks guide — Limitations and troubleshooting.” code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting 

  8. Anthropic, “Hooks reference — Security considerations.” code.claude.com/docs/en/hooks#security-considerations 

  9. Anthropic, “Claude Code settings.” code.claude.com/docs/en/settings 

관련 게시물

Codex CLI vs Claude Code 2026: 아키텍처, 가격, 그리고 중국 접근성

Codex CLI와 Claude Code의 심층 비교: 커널 샌드박싱 vs 26개 후크 거버넌스, Opus 4.7 vs GPT-5.4 벤치마크, CNY 예시를 포함한 토큰당 가격, 그리고 중국에서의 클라우드 접근성(…

20 분 소요

Claude Code Hooks: 95개의 Hook이 각각 존재하는 이유

Claude Code용으로 95개의 hook을 만들었습니다. 각각은 무언가 잘못되었기 때문에 존재합니다. 그 기원 이야기와 거기서 발생한 아키텍처를 소개합니다.

6 분 소요