예제가 포함된 빠른 시작 가이드는 Claude Code 훅 시작하기를 참조하세요.

구성

Claude Code 훅은 설정 파일에서 구성됩니다:

  • ~/.claude/settings.json - 사용자 설정
  • .claude/settings.json - 프로젝트 설정
  • .claude/settings.local.json - 로컬 프로젝트 설정 (커밋되지 않음)
  • 엔터프라이즈 관리 정책 설정

구조

훅은 매처별로 구성되며, 각 매처는 여러 훅을 가질 수 있습니다:

{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolPattern",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here"
          }
        ]
      }
    ]
  }
}
  • matcher: 도구 이름과 일치시킬 패턴, 대소문자 구분 (PreToolUsePostToolUse에만 적용)
    • 단순 문자열은 정확히 일치: Write는 Write 도구만 일치
    • 정규식 지원: Edit|Write 또는 Notebook.*
    • 모든 도구와 일치시키려면 * 사용. 빈 문자열("")을 사용하거나 matcher를 비워둘 수도 있습니다.
  • hooks: 패턴이 일치할 때 실행할 명령 배열
    • type: 현재 "command"만 지원
    • command: 실행할 bash 명령 ($CLAUDE_PROJECT_DIR 환경 변수 사용 가능)
    • timeout: (선택사항) 특정 명령을 취소하기 전에 실행할 시간(초)

매처를 사용하지 않는 UserPromptSubmit, Notification, Stop, SubagentStop과 같은 이벤트의 경우 matcher 필드를 생략할 수 있습니다:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/prompt-validator.py"
          }
        ]
      }
    ]
  }
}

프로젝트별 훅 스크립트

환경 변수 CLAUDE_PROJECT_DIR(Claude Code가 훅 명령을 실행할 때만 사용 가능)을 사용하여 프로젝트에 저장된 스크립트를 참조할 수 있으며, Claude의 현재 디렉토리와 관계없이 작동하도록 보장합니다:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-style.sh"
          }
        ]
      }
    ]
  }
}

훅 이벤트

PreToolUse

Claude가 도구 매개변수를 생성한 후, 도구 호출을 처리하기 전에 실행됩니다.

일반적인 매처:

  • Task - 에이전트 작업
  • Bash - 셸 명령
  • Glob - 파일 패턴 매칭
  • Grep - 콘텐츠 검색
  • Read - 파일 읽기
  • Edit, MultiEdit - 파일 편집
  • Write - 파일 쓰기
  • WebFetch, WebSearch - 웹 작업

PostToolUse

도구가 성공적으로 완료된 직후에 실행됩니다.

PreToolUse와 동일한 매처 값을 인식합니다.

Notification

Claude Code가 알림을 보낼 때 실행됩니다. 알림은 다음과 같은 경우에 전송됩니다:

  1. Claude가 도구 사용 권한이 필요할 때. 예: “Claude needs your permission to use Bash”
  2. 프롬프트 입력이 최소 60초 동안 유휴 상태일 때. “Claude is waiting for your input”

UserPromptSubmit

사용자가 프롬프트를 제출할 때, Claude가 처리하기 전에 실행됩니다. 이를 통해 프롬프트/대화를 기반으로 추가 컨텍스트를 추가하거나, 프롬프트를 검증하거나, 특정 유형의 프롬프트를 차단할 수 있습니다.

Stop

메인 Claude Code 에이전트가 응답을 완료했을 때 실행됩니다. 사용자 중단으로 인한 중지의 경우에는 실행되지 않습니다.

SubagentStop

Claude Code 서브에이전트(Task 도구 호출)가 응답을 완료했을 때 실행됩니다.

PreCompact

Claude Code가 압축 작업을 실행하려고 할 때 실행됩니다.

매처:

  • manual - /compact에서 호출
  • auto - 자동 압축에서 호출 (전체 컨텍스트 창으로 인해)

훅 입력

훅은 세션 정보와 이벤트별 데이터가 포함된 JSON 데이터를 stdin을 통해 받습니다:

{
  // 공통 필드
  session_id: string
  transcript_path: string  // 대화 JSON 경로
  cwd: string              // 훅이 호출될 때의 현재 작업 디렉토리

  // 이벤트별 필드
  hook_event_name: string
  ...
}

PreToolUse 입력

tool_input의 정확한 스키마는 도구에 따라 다릅니다.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "file content"
  }
}

PostToolUse 입력

tool_inputtool_response의 정확한 스키마는 도구에 따라 다릅니다.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "file content"
  },
  "tool_response": {
    "filePath": "/path/to/file.txt",
    "success": true
  }
}

Notification 입력

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Notification",
  "message": "Task completed successfully"
}

UserPromptSubmit 입력

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "Write a function to calculate the factorial of a number"
}

Stop 및 SubagentStop 입력

stop_hook_active는 Claude Code가 이미 stop 훅의 결과로 계속 진행 중일 때 true입니다. Claude Code가 무한히 실행되는 것을 방지하려면 이 값을 확인하거나 대화록을 처리하세요.

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "hook_event_name": "Stop",
  "stop_hook_active": true
}

PreCompact 입력

manual의 경우 custom_instructions는 사용자가 /compact에 전달하는 내용에서 가져옵니다. auto의 경우 custom_instructions는 비어 있습니다.

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "hook_event_name": "PreCompact",
  "trigger": "manual",
  "custom_instructions": ""
}

훅 출력

훅이 Claude Code로 출력을 반환하는 방법은 두 가지가 있습니다. 출력은 차단 여부와 Claude 및 사용자에게 표시되어야 하는 피드백을 전달합니다.

간단한 방법: 종료 코드

훅은 종료 코드, stdout, stderr을 통해 상태를 전달합니다:

  • 종료 코드 0: 성공. stdout은 대화록 모드(CTRL-R)에서 사용자에게 표시되지만, UserPromptSubmit의 경우 stdout이 컨텍스트에 추가됩니다.
  • 종료 코드 2: 차단 오류. stderr이 Claude에게 자동으로 피드백되어 처리됩니다. 아래 훅 이벤트별 동작을 참조하세요.
  • 기타 종료 코드: 비차단 오류. stderr이 사용자에게 표시되고 실행이 계속됩니다.

주의: Claude Code는 종료 코드가 0일 때 stdout을 보지 않습니다. 단, stdout이 컨텍스트로 주입되는 UserPromptSubmit 훅은 예외입니다.

종료 코드 2 동작

훅 이벤트동작
PreToolUse도구 호출을 차단하고 stderr을 Claude에게 표시
PostToolUsestderr을 Claude에게 표시 (도구는 이미 실행됨)
Notification해당 없음, stderr을 사용자에게만 표시
UserPromptSubmit프롬프트 처리를 차단하고 프롬프트를 지우며 stderr을 사용자에게만 표시
Stop중지를 차단하고 stderr을 Claude에게 표시
SubagentStop중지를 차단하고 stderr을 Claude 서브에이전트에게 표시
PreCompact해당 없음, stderr을 사용자에게만 표시

고급 방법: JSON 출력

훅은 더 정교한 제어를 위해 stdout에서 구조화된 JSON을 반환할 수 있습니다:

공통 JSON 필드

모든 훅 유형은 다음 선택적 필드를 포함할 수 있습니다:

{
  "continue": true, // 훅 실행 후 Claude가 계속해야 하는지 여부 (기본값: true)
  "stopReason": "string" // continue가 false일 때 표시되는 메시지
  "suppressOutput": true, // 대화록 모드에서 stdout 숨기기 (기본값: false)
}

continue가 false이면 훅 실행 후 Claude가 처리를 중지합니다.

  • PreToolUse의 경우, 이는 특정 도구 호출만 차단하고 Claude에게 자동 피드백을 제공하는 "permissionDecision": "deny"와 다릅니다.
  • PostToolUse의 경우, 이는 Claude에게 자동화된 피드백을 제공하는 "decision": "block"과 다릅니다.
  • UserPromptSubmit의 경우, 이는 프롬프트가 처리되는 것을 방지합니다.
  • StopSubagentStop의 경우, 이는 모든 "decision": "block" 출력보다 우선합니다.
  • 모든 경우에 "continue" = false는 모든 "decision": "block" 출력보다 우선합니다.

stopReasoncontinue와 함께 사용자에게 표시되는 이유를 제공하며, Claude에게는 표시되지 않습니다.

PreToolUse 결정 제어

PreToolUse 훅은 도구 호출이 진행되는지 여부를 제어할 수 있습니다.

  • "allow"는 권한 시스템을 우회합니다. permissionDecisionReason은 사용자에게 표시되지만 Claude에게는 표시되지 않습니다. (더 이상 사용되지 않는 "approve" 값 + reason도 동일한 동작을 합니다.)
  • "deny"는 도구 호출이 실행되는 것을 방지합니다. permissionDecisionReason은 Claude에게 표시됩니다. ("block" 값 + reason도 동일한 동작을 합니다.)
  • "ask"는 사용자에게 UI에서 도구 호출을 확인하도록 요청합니다. permissionDecisionReason은 사용자에게 표시되지만 Claude에게는 표시되지 않습니다.
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow" | "deny" | "ask",
    "permissionDecisionReason": "My reason here (shown to user)"
  },
  "decision": "approve" | "block" | undefined, // PreToolUse에서는 더 이상 사용되지 않지만 여전히 지원됨
  "reason": "Explanation for decision" // PreToolUse에서는 더 이상 사용되지 않지만 여전히 지원됨
}

PostToolUse 결정 제어

PostToolUse 훅은 도구 호출이 진행되는지 여부를 제어할 수 있습니다.

  • "block"은 자동으로 Claude에게 reason을 프롬프트합니다.
  • undefined는 아무것도 하지 않습니다. reason은 무시됩니다.
{
  "decision": "block" | undefined,
  "reason": "Explanation for decision"
}

UserPromptSubmit 결정 제어

UserPromptSubmit 훅은 사용자 프롬프트가 처리되는지 여부를 제어할 수 있습니다.

  • "block"은 프롬프트가 처리되는 것을 방지합니다. 제출된 프롬프트는 컨텍스트에서 지워집니다. "reason"은 사용자에게 표시되지만 컨텍스트에 추가되지 않습니다.
  • undefined는 프롬프트가 정상적으로 진행되도록 허용합니다. "reason"은 무시됩니다.
  • "hookSpecificOutput.additionalContext"는 차단되지 않은 경우 문자열을 컨텍스트에 추가합니다.
{
  "decision": "block" | undefined,
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "My additional context here"
  }
}

Stop/SubagentStop 결정 제어

StopSubagentStop 훅은 Claude가 계속해야 하는지 여부를 제어할 수 있습니다.

  • "block"은 Claude가 중지하는 것을 방지합니다. Claude가 어떻게 진행해야 하는지 알 수 있도록 reason을 채워야 합니다.
  • undefined는 Claude가 중지하도록 허용합니다. reason은 무시됩니다.
{
  "decision": "block" | undefined,
  "reason": "Must be provided when Claude is blocked from stopping"
}

종료 코드 예제: Bash 명령 검증

#!/usr/bin/env python3
import json
import re
import sys

# 검증 규칙을 (정규식 패턴, 메시지) 튜플 목록으로 정의
VALIDATION_RULES = [
    (
        r"\bgrep\b(?!.*\|)",
        "Use 'rg' (ripgrep) instead of 'grep' for better performance and features",
    ),
    (
        r"\bfind\s+\S+\s+-name\b",
        "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",
    ),
]


def validate_command(command: str) -> list[str]:
    issues = []
    for pattern, message in VALIDATION_RULES:
        if re.search(pattern, command):
            issues.append(message)
    return issues


try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
    sys.exit(1)

tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
command = tool_input.get("command", "")

if tool_name != "Bash" or not command:
    sys.exit(1)

# 명령 검증
issues = validate_command(command)

if issues:
    for message in issues:
        print(f"• {message}", file=sys.stderr)
    # 종료 코드 2는 도구 호출을 차단하고 stderr을 Claude에게 표시
    sys.exit(2)

JSON 출력 예제: 컨텍스트 추가 및 검증을 위한 UserPromptSubmit

UserPromptSubmit 훅의 경우 두 가지 방법으로 컨텍스트를 주입할 수 있습니다:

  • stdout과 함께 종료 코드 0: Claude가 컨텍스트를 봅니다 (UserPromptSubmit의 특별한 경우)
  • JSON 출력: 동작에 대한 더 많은 제어를 제공합니다
#!/usr/bin/env python3
import json
import sys
import re
import datetime

# stdin에서 입력 로드
try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
    sys.exit(1)

prompt = input_data.get("prompt", "")

# 민감한 패턴 확인
sensitive_patterns = [
    (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),
]

for pattern, message in sensitive_patterns:
    if re.search(pattern, prompt):
        # JSON 출력을 사용하여 특정 이유로 차단
        output = {
            "decision": "block",
            "reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information."
        }
        print(json.dumps(output))
        sys.exit(0)

# 컨텍스트에 현재 시간 추가
context = f"Current time: {datetime.datetime.now()}"
print(context)

"""
다음도 동등합니다:
print(json.dumps({
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": context,
  },
}))
"""

# 추가 컨텍스트와 함께 프롬프트가 진행되도록 허용
sys.exit(0)

JSON 출력 예제: 승인이 포함된 PreToolUse

#!/usr/bin/env python3
import json
import sys

# stdin에서 입력 로드
try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
    sys.exit(1)

tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})

# 예제: 문서 파일에 대한 파일 읽기 자동 승인
if tool_name == "Read":
    file_path = tool_input.get("file_path", "")
    if file_path.endswith((".md", ".mdx", ".txt", ".json")):
        # JSON 출력을 사용하여 도구 호출 자동 승인
        output = {
            "decision": "approve",
            "reason": "Documentation file auto-approved",
            "suppressOutput": True  # 대화록 모드에서 표시하지 않음
        }
        print(json.dumps(output))
        sys.exit(0)

# 다른 경우에는 일반적인 권한 흐름이 진행되도록 함
sys.exit(0)

MCP 도구와 함께 작업하기

Claude Code 훅은 Model Context Protocol (MCP) 도구와 원활하게 작동합니다. MCP 서버가 도구를 제공할 때, 훅에서 일치시킬 수 있는 특별한 명명 패턴으로 나타납니다.

MCP 도구 명명

MCP 도구는 mcp__<server>__<tool> 패턴을 따릅니다. 예를 들어:

  • mcp__memory__create_entities - Memory 서버의 create entities 도구
  • mcp__filesystem__read_file - Filesystem 서버의 read file 도구
  • mcp__github__search_repositories - GitHub 서버의 search 도구

MCP 도구에 대한 훅 구성

특정 MCP 도구나 전체 MCP 서버를 대상으로 할 수 있습니다:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
          }
        ]
      },
      {
        "matcher": "mcp__.*__write.*",
        "hooks": [
          {
            "type": "command",
            "command": "/home/user/scripts/validate-mcp-write.py"
          }
        ]
      }
    ]
  }
}

예제

코드 포맷팅, 알림, 파일 보호를 포함한 실용적인 예제는 시작 가이드의 더 많은 예제를 참조하세요.

보안 고려사항

면책조항

본인 책임하에 사용: Claude Code 훅은 시스템에서 임의의 셸 명령을 자동으로 실행합니다. 훅을 사용함으로써 다음을 인정합니다:

  • 구성하는 명령에 대해 전적으로 책임집니다
  • 훅은 사용자 계정이 액세스할 수 있는 모든 파일을 수정, 삭제 또는 액세스할 수 있습니다
  • 악의적이거나 잘못 작성된 훅은 데이터 손실이나 시스템 손상을 일으킬 수 있습니다
  • Anthropic은 어떠한 보증도 제공하지 않으며 훅 사용으로 인한 손해에 대해 책임을 지지 않습니다
  • 프로덕션 사용 전에 안전한 환경에서 훅을 철저히 테스트해야 합니다

구성에 추가하기 전에 항상 훅 명령을 검토하고 이해하세요.

보안 모범 사례

더 안전한 훅을 작성하기 위한 주요 관행은 다음과 같습니다:

  1. 입력 검증 및 정제 - 입력 데이터를 맹목적으로 신뢰하지 마세요
  2. 항상 셸 변수를 인용 - $VAR가 아닌 "$VAR" 사용
  3. 경로 순회 차단 - 파일 경로에서 .. 확인
  4. 절대 경로 사용 - 스크립트에 대한 전체 경로 지정 (프로젝트 경로에는 $CLAUDE_PROJECT_DIR 사용)
  5. 민감한 파일 건너뛰기 - .env, .git/, 키 등 피하기

구성 안전성

설정 파일의 훅에 대한 직접 편집은 즉시 적용되지 않습니다. Claude Code는:

  1. 시작 시 훅의 스냅샷을 캡처합니다
  2. 세션 전체에서 이 스냅샷을 사용합니다
  3. 훅이 외부에서 수정되면 경고합니다
  4. 변경사항이 적용되려면 /hooks 메뉴에서 검토가 필요합니다

이는 악의적인 훅 수정이 현재 세션에 영향을 미치는 것을 방지합니다.

훅 실행 세부사항

  • 타임아웃: 기본적으로 60초 실행 제한, 명령별로 구성 가능.
    • 개별 명령의 타임아웃은 다른 명령에 영향을 주지 않습니다.
  • 병렬화: 일치하는 모든 훅이 병렬로 실행됩니다
  • 환경: Claude Code의 환경과 함께 현재 디렉토리에서 실행
    • CLAUDE_PROJECT_DIR 환경 변수를 사용할 수 있으며 프로젝트 루트 디렉토리의 절대 경로를 포함합니다
  • 입력: stdin을 통한 JSON
  • 출력:
    • PreToolUse/PostToolUse/Stop: 대화록에 진행 상황 표시 (Ctrl-R)
    • Notification: 디버그에만 로그 (--debug)

디버깅

기본 문제 해결

훅이 작동하지 않는 경우:

  1. 구성 확인 - /hooks를 실행하여 훅이 등록되었는지 확인
  2. 구문 검증 - JSON 설정이 유효한지 확인
  3. 명령 테스트 - 먼저 훅 명령을 수동으로 실행
  4. 권한 확인 - 스크립트가 실행 가능한지 확인
  5. 로그 검토 - claude --debug를 사용하여 훅 실행 세부사항 확인

일반적인 문제:

  • 인용부호가 이스케이프되지 않음 - JSON 문자열 내에서 \" 사용
  • 잘못된 매처 - 도구 이름이 정확히 일치하는지 확인 (대소문자 구분)
  • 명령을 찾을 수 없음 - 스크립트에 전체 경로 사용

고급 디버깅

복잡한 훅 문제의 경우:

  1. 훅 실행 검사 - claude --debug를 사용하여 자세한 훅 실행 확인
  2. JSON 스키마 검증 - 외부 도구로 훅 입력/출력 테스트
  3. 환경 변수 확인 - Claude Code의 환경이 올바른지 확인
  4. 엣지 케이스 테스트 - 비정상적인 파일 경로나 입력으로 훅 시도
  5. 시스템 리소스 모니터링 - 훅 실행 중 리소스 고갈 확인
  6. 구조화된 로깅 사용 - 훅 스크립트에서 로깅 구현

디버그 출력 예제

claude --debug를 사용하여 훅 실행 세부사항을 확인하세요:

[DEBUG] Executing hooks for PostToolUse:Write
[DEBUG] Getting matching hook commands for PostToolUse with query: Write
[DEBUG] Found 1 hook matchers in settings
[DEBUG] Matched 1 hooks for query "Write"
[DEBUG] Found 1 hook commands to execute
[DEBUG] Executing hook command: <Your command> with timeout 60000ms
[DEBUG] Hook command completed with status 0: <Your stdout>

진행 메시지는 대화록 모드(Ctrl-R)에 나타나며 다음을 보여줍니다:

  • 실행 중인 훅
  • 실행되는 명령
  • 성공/실패 상태
  • 출력 또는 오류 메시지