Claude Code 훅은 LLM이 가끔 잊는 일을 결정론으로 보장한다

같은 작업을 두 번 시켰는데 결과가 다르게 나온 적이 있을 것이다. Claude에게 버그를 고쳐 달라고 했더니 어느 날은 테스트까지 돌리지만 다음 날은 코드만 수정하고 끝낸다. 이 차이는 모델의 잘못이 아니라 LLM이 본질적으로 확률 기반으로 다음 토큰을 고르기 때문이다. 그러나 실제 개발 현장은 결정론을 요구한다. 코드 수정 후에는 항상 포매터가 돌아야 하고, .env 파일은 절대 수정되면 안 되며, 권한 요청이 뜨면 즉시 알림이 가야 한다. Claude Code 훅(Hook)은 바로 이 결정론을 위한 장치다. LLM의 판단에 맡기는 게 아니라 라이프사이클의 특정 지점에서 항상 실행되는 셸 명령을 등록한다

훅이 해결하는 문제와 다섯 가지 사용 사례

훅의 본질은 두 가지로 압축된다. 첫째, LLM이 어떤 결정을 내리든 특정 시점에 반드시 실행된다. 둘째, 결과에 따라 작업을 차단하거나 피드백을 주입할 수 있다

공식 문서가 제시하는 대표 사용 사례는 다섯 가지다

  • 알림: 권한 요청이나 작업 완료 시점에 데스크톱·모바일로 푸시
  • 자동 포매팅: 파일 편집 직후 Prettier 등 포매터를 자동 실행
  • 감사 로그: 모든 Bash 명령이나 파일 수정을 파일에 기록
  • 자동 피드백: 컨벤션을 위반한 코드를 차단하고 Claude에 수정 사유 전달
  • 사용자 정의 권한: .env, package-lock.json, .git/ 등 보호 대상 편집을 LLM 모드와 무관하게 차단

마지막 항목이 특히 강력하다. PreToolUse 훅이 차단을 결정하면 --dangerously-skip-permissions 같은 우회 옵션이 켜져 있어도 도구 호출이 막힌다. 정책 강제용 도구로는 훅이 가장 윗단에 위치한다

훅은 이제 settings.json에 직접 등록한다

이전 버전의 Claude Code는 /hooks 슬래시 명령어로 훅을 직접 추가할 수 있었다. 현재 /hooks는 등록된 목록을 보여주는 read-only 메뉴로 바뀌었다. 등록과 수정은 두 가지 경로로 한다

  • 직접 편집: .claude/settings.json 또는 ~/.claude/settings.json
  • Claude에 위임: “알림 훅을 등록해줘”라고 요청하면 Claude가 settings.json을 대신 수정

어느 쪽이든 결과는 같은 JSON 파일에 기록된다

설정 위치별 적용 범위는 다음과 같다

위치범위Git 공유
~/.claude/settings.json모든 프로젝트불가
.claude/settings.json단일 프로젝트가능 (커밋)
.claude/settings.local.json단일 프로젝트불가 (gitignore)
관리되는 정책 설정조직 전체관리자 제어
플러그인 hooks/hooks.json플러그인 활성 시플러그인 동봉

팀이 공유할 규칙은 .claude/settings.json에 두고 Git에 커밋한다. 개인 단축은 ~/.claude/settings.json에 둔다

훅의 기본 구조와 첫 실습

훅 JSON은 세 층으로 구성된다

{
  "hooks": {
    "이벤트이름": [
      {
        "matcher": "어떤 경우에만 실행할지 필터",
        "hooks": [
          { "type": "command", "command": "실행할 셸 명령" }
        ]
      }
    ]
  }
}

세 가지만 기억하면 된다

  • 이벤트이름: 언제 실행할지 (예: PreToolUse, Stop, Notification)
  • matcher: 어떤 도구·상황에서만 발동할지 (Bash, Edit|Write, 빈 문자열은 전부)
  • command: 어떤 셸 명령을 실행할지

가장 단순한 실습으로, Bash 도구가 호출되기 직전에 한 줄을 파일에 남기는 훅을 만들어 본다. .claude/settings.json에 다음을 추가한다

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'test hook' >> hook-test.txt"
          }
        ]
      }
    ]
  }
}

Claude Code를 재실행하고 “현재 디렉터리 목록을 보여줘”처럼 Bash가 호출될 만한 요청을 던지면, 그때마다 hook-test.txt에 한 줄씩 추가된다. Claude가 Bash를 세 번 사용하면 세 줄이 쌓인다

주요 훅 이벤트 한눈에 보기

훅 이벤트는 라이프사이클의 모든 핵심 지점을 덮는다. 자주 쓰는 이벤트만 정리하면 다음과 같다.

이벤트발생 시점matcher 입력
PreToolUseClaude가 도구를 호출하기 직전도구 이름 (Bash, Edit|Write)
PostToolUse도구 호출이 성공한 직후도구 이름
UserPromptSubmit사용자가 프롬프트를 제출한 직후없음
NotificationClaude가 입력·권한을 기다릴 때알림 종류
StopClaude의 응답이 끝났을 때없음
SubagentStop서브에이전트 작업이 끝났을 때에이전트 이름
PreCompact컨텍스트 압축 직전manual, auto
SessionStart새 세션 시작 또는 재개startup, resume, compact
SessionEnd세션이 종료될 때종료 사유

이 외에 FileChanged, CwdChanged, ConfigChange, PermissionRequest 등 더 세밀한 이벤트도 있다. 전체 목록은 공식 문서의 Hooks 참조에서 확인한다

Stop은 사용자가 ESC로 중단했을 때는 발생하지 않는다. 작업 완료에만 반응하므로 자동 커밋·백업 같은 마무리 작업에 쓰기 좋다

실전 – Slack으로 권한 요청과 작업 완료 알림 받기

가장 체감 효과가 큰 활용은 모바일 푸시 알림이다. 복잡한 작업을 맡기고 자리를 비웠다가 권한 승인 요청을 놓쳐 본 사람이라면 한 번에 이해한다. Slack Incoming Webhook URL을 발급받고, NotificationStop 두 이벤트에 webhook 호출 셸 스크립트를 연결하면 끝난다

먼저 Slack 워크스페이스에서 Incoming Webhook 앱을 채널에 추가해 URL을 발급받는다. URL은 민감 정보이므로 프로젝트 .env에 보관한다

# .env
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...

훅 스크립트는 .claude/hooks/notify.sh에 둔다

#!/bin/bash
# notify.sh
INPUT=$(cat)
EVENT=$(echo "$INPUT" | jq -r '.hook_event_name')

set -a
source "$CLAUDE_PROJECT_DIR/.env" 2>/dev/null
set +a

curl -s -X POST -H 'Content-Type: application/json' \
  --data "{\"text\":\"[$EVENT] Claude Code 알림\"}" \
  "$SLACK_WEBHOOK_URL" > /dev/null

실행 권한을 부여한다

chmod +x .claude/hooks/notify.sh

.claude/settings.json에 두 이벤트를 등록한다

{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/notify.sh" }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/notify.sh" }
        ]
      }
    ]
  }
}

Claude가 권한 승인을 요청하면 Notification 훅이 발동하고, 응답이 끝나면 Stop 훅이 발동한다. 모바일 Slack 앱이 두 시점 모두 푸시로 받아 준다. 자리를 비워도 작업 흐름이 끊기지 않는다

여기서 한 가지 주의할 점이 있다. 계획만 보고 Claude가 “PreToolUse·PostToolUse를 쓰자”고 잘못 제안할 수 있다. 권한 요청은 Notification, 응답 완료는 Stop이라는 사실을 사람이 명확히 짚어 줘야 한다. 훅 이벤트의 정확한 의미는 LLM에 맡기지 말고 공식 문서로 직접 확인하는 편이 안전하다

jq로 훅 입력 JSON을 활용한다

훅 스크립트는 stdin으로 JSON을 받는다. 모든 이벤트가 공통으로 가지는 필드는 session_id, cwd, hook_event_name이고, 이벤트별로 추가 필드가 붙는다. PreToolUsetool_nametool_input을, Notificationmessage를 추가로 전달한다

이 JSON에서 원하는 값을 꺼내려면 jq가 가장 짧다

  • macOS: brew install jq
  • Windows (10/11): winget install jqlang.jq

winget은 Windows 10/11에 기본 내장되어 있어 별도 패키지 매니저 설치 단계가 필요 없다

Notification 훅에서 메시지 본문만 뽑아 Slack으로 보내려면 다음과 같이 작성한다

#!/bin/bash
INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message')

curl -s -X POST -H 'Content-Type: application/json' \
  --data "{\"text\":\"권한 요청: $MESSAGE\"}" \
  "$SLACK_WEBHOOK_URL"

Stop 훅에는 message 같은 필드가 없다. 대신 공통 필드인 hook_event_name을 활용해 어떤 이벤트가 끝났는지 표시한다

EVENT=$(echo "$INPUT" | jq -r '.hook_event_name')

훅이 잘못 동작할 때 가장 흔한 원인 중 하나가 jq 미설치다. jq: command not found가 보이면 위 한 줄로 해결된다. 입력 데이터 구조를 모르겠다면 cat만 해서 파일에 한 번 덤프해 본 뒤 jq 표현식을 짜는 흐름이 가장 빠르다

종료 코드와 JSON 출력으로 동작을 제어한다

훅 스크립트는 종료 코드와 stdout으로 Claude Code에 결과를 전달한다

종료 코드의미
0작업 진행. UserPromptSubmit·SessionStart는 stdout 텍스트가 Claude 컨텍스트에 추가됨
2작업 차단. stderr 메시지가 Claude에 피드백으로 전달
그 외작업 진행하되 트랜스크립트에 오류 표시

차단이 필요한 시나리오에서는 exit 2가 가장 단순하다. .env 편집을 막는 훅은 다음 한 토막이면 된다

INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [[ "$FILE" == *".env"* ]]; then
  echo "Blocked: .env 편집은 금지되어 있습니다." >&2
  exit 2
fi
exit 0

더 정교한 제어가 필요하면 stdout에 JSON을 출력한다. PreToolUse에서 도구 호출을 거부하면서 사유를 함께 전달하는 형태는 다음과 같다

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "rg를 사용하라. grep은 성능상 권장되지 않는다."
  }
}

permissionDecisionallow, deny, ask 중 하나를 갖는다. 단, allow로 설정해도 settings의 거부 규칙은 우회되지 않는다. 훅은 제한을 더 강화할 수는 있어도 권한 정책 자체를 풀어 주지는 못한다

정리

정리하면 훅은 LLM의 확률적 판단 위에 결정론을 얹는 가장 빠른 도구다. 자동화·차단·알림 중 어느 하나라도 매번 일어나야 한다면 메모리 파일이나 프롬프트로 부탁하지 말고 훅으로 못 박는다. settings.json 한 줄 편집으로 끝난다

공식문서 – Hooks 참조, hooks를 사용하여 워크플로우 자동화

출처 – 인프런 [클로드 코드 완벽 마스터: AI 개발 워크플로우 기초부터 실전까지]