Logo
본문으로 이동
고급14분 소요hooksautomationworkflow

Hooks 시스템

읽는 시간: 14분 | 난이도: 고급자

Claude Code의 Hooks를 사용하면 세션 생명주기의 특정 시점에서 셸 명령, LLM 프롬프트, 에이전트를 자동으로 실행할 수 있습니다.

Hooks란?

Hooks는 Claude Code의 생명주기 특정 시점에서 자동 실행되는 사용자 정의 핸들러입니다. 파일을 쓸 때 자동으로 린트를 실행하거나, 위험한 명령을 차단하거나, 세션 시작 시 개발 컨텍스트를 주입하는 등의 자동화를 구현할 수 있습니다.

Hook 생명주기

Hook이 실행되는 흐름은 다음과 같습니다:

이벤트 발생 → Matcher 확인 → Hook 핸들러 실행 → Claude Code가 결과에 따라 동작

예를 들어, PreToolUse 이벤트에 Bash matcher를 설정하면:

  1. Claude가 Bash 도구를 호출하려 할 때 PreToolUse 이벤트 발생
  2. Matcher "Bash"가 도구 이름과 일치하는지 확인
  3. 일치하면 Hook 핸들러(셸 스크립트 등)가 실행됨
  4. 핸들러의 exit code와 stdout JSON에 따라 Claude Code가 허용/차단/수정

설정 방법

Hooks는 JSON 설정 파일에 정의합니다. 3단계 중첩 구조로 되어 있습니다:

  1. 이벤트: 어떤 시점에 반응할지 (PreToolUse, Stop 등)
  2. Matcher 그룹: 언제 실행할지 필터링 ("Bash 도구에만" 등)
  3. Hook 핸들러 배열: 매칭되면 실행할 명령/프롬프트/에이전트

기본 구조

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

설정 파일 위치

Hook을 정의하는 위치에 따라 적용 범위가 달라집니다:

위치 범위 공유 가능
~/.claude/settings.json 모든 프로젝트 아니요, 로컬 전용
.claude/settings.json 단일 프로젝트 예, 저장소에 커밋 가능
.claude/settings.local.json 단일 프로젝트 아니요, gitignore됨
관리 정책 설정 조직 전체 예, 관리자 제어
플러그인 hooks/hooks.json 플러그인 활성화 시 예, 플러그인에 번들
Skill/Agent 프론트매터 컴포넌트 활성 시 예, 컴포넌트 파일에 정의

/hooks 메뉴

Claude Code에서 /hooks를 입력하면 대화형 Hook 관리자가 열립니다. 설정 파일을 직접 편집하지 않고도 Hook을 확인, 추가, 삭제할 수 있습니다. 각 Hook에는 출처를 나타내는 접두사가 표시됩니다:

  • [User]: ~/.claude/settings.json
  • [Project]: .claude/settings.json
  • [Local]: .claude/settings.local.json
  • [Plugin]: 플러그인의 hooks/hooks.json (읽기 전용)

이벤트 레퍼런스

Claude Code 세션의 각 시점에서 발생하는 이벤트 전체 목록입니다:

이벤트 발생 시점
SessionStart 세션이 시작되거나 재개될 때
UserPromptSubmit 사용자가 프롬프트를 제출하고, Claude가 처리하기 전
PreToolUse 도구 호출이 실행되기 전. 차단 가능
PermissionRequest 권한 대화상자가 표시될 때
PostToolUse 도구 호출이 성공적으로 완료된 후
PostToolUseFailure 도구 호출이 실패한 후
Notification Claude Code가 알림을 보낼 때
SubagentStart 서브에이전트가 생성될 때
SubagentStop 서브에이전트가 완료될 때
Stop Claude가 응답을 마쳤을 때
TeammateIdle 에이전트 팀 동료가 유휴 상태로 전환되려 할 때
TaskCompleted 태스크가 완료로 표시될 때
ConfigChange 세션 중 설정 파일이 변경될 때
WorktreeCreate --worktree 또는 isolation: "worktree"로 워크트리 생성 시
WorktreeRemove 워크트리가 제거될 때 (세션 종료 또는 서브에이전트 완료 시)
PreCompact 컨텍스트 압축 전
SessionEnd 세션이 종료될 때

Exit Code 2의 이벤트별 동작

Exit code 2는 "중단하라"는 신호입니다. 이벤트에 따라 효과가 다릅니다:

이벤트 차단 가능? Exit 2 시 동작
PreToolUse 도구 호출 차단
PermissionRequest 권한 거부
UserPromptSubmit 프롬프트 처리 차단 및 삭제
Stop Claude 정지 방지, 대화 계속
SubagentStop 서브에이전트 정지 방지
TeammateIdle 유휴 전환 방지 (작업 계속)
TaskCompleted 태스크 완료 표시 방지
ConfigChange 설정 변경 차단 (policy_settings 제외)
PostToolUse 아니요 stderr를 Claude에게 표시 (이미 실행됨)
PostToolUseFailure 아니요 stderr를 Claude에게 표시
Notification 아니요 stderr를 사용자에게만 표시
SubagentStart 아니요 stderr를 사용자에게만 표시
SessionStart 아니요 stderr를 사용자에게만 표시
SessionEnd 아니요 stderr를 사용자에게만 표시
PreCompact 아니요 stderr를 사용자에게만 표시
WorktreeCreate 0이 아닌 exit code면 생성 실패
WorktreeRemove 아니요 실패는 디버그 모드에서만 기록

Hook 타입

Hook 핸들러는 세 가지 타입이 있습니다.

Command Hooks (type: "command")

셸 명령을 실행합니다. 이벤트의 JSON 입력을 stdin으로 받고, exit code와 stdout으로 결과를 전달합니다.

{
  "type": "command",
  "command": ".claude/hooks/my-script.sh",
  "timeout": 600
}
필드 필수 설명
type "command"
command 실행할 셸 명령
timeout 아니요 초 단위 타임아웃. 기본값: 600
async 아니요 true면 백그라운드 실행
statusMessage 아니요 Hook 실행 중 표시될 스피너 메시지

stdin JSON 입력

모든 Hook 이벤트는 stdin으로 공통 필드가 포함된 JSON을 받습니다:

필드 설명
session_id 현재 세션 식별자
transcript_path 대화 JSON 파일 경로
cwd Hook 실행 시 현재 작업 디렉터리
permission_mode 현재 권한 모드: "default", "plan", "acceptEdits", "dontAsk", "bypassPermissions"
hook_event_name 발생한 이벤트 이름

예를 들어, PreToolUse Hook이 Bash 명령에 대해 받는 stdin:

{
  "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"
  }
}

Exit Code 의미

Exit Code 의미 동작
0 성공 stdout에서 JSON 출력 파싱. 대부분 이벤트에서 stdout은 verbose 모드(Ctrl+O)에서만 표시. UserPromptSubmitSessionStart는 stdout이 Claude 컨텍스트에 추가됨
2 차단 에러 stdout/JSON 무시. stderr 텍스트가 Claude에게 에러 메시지로 전달
기타 비차단 에러 stderr가 verbose 모드에서 표시되고 실행 계속

stdout JSON 출력

Exit 0으로 종료하면서 JSON을 stdout에 출력하여 세밀한 제어가 가능합니다:

필드 기본값 설명
continue true false면 Claude가 완전히 처리를 중단
stopReason 없음 continuefalse일 때 사용자에게 표시할 메시지
suppressOutput false true면 verbose 모드 출력 숨김
systemMessage 없음 사용자에게 표시할 경고 메시지
{ "continue": false, "stopReason": "빌드 실패. 에러를 수정한 후 계속하세요" }

Prompt Hooks (type: "prompt")

LLM에 프롬프트를 보내 단일 턴으로 평가합니다. 모델이 yes/no 결정을 JSON으로 반환합니다.

{
  "type": "prompt",
  "prompt": "Claude가 멈춰야 할지 평가하세요: $ARGUMENTS. 모든 작업이 완료되었는지 확인하세요.",
  "timeout": 30
}
필드 필수 설명
type "prompt"
prompt LLM에 보낼 프롬프트. $ARGUMENTS는 Hook 입력 JSON으로 대체됨
model 아니요 평가에 사용할 모델. 기본값: 빠른 모델
timeout 아니요 초 단위 타임아웃. 기본값: 30

LLM 응답 형식:

{
  "ok": true,
  "reason": "결정에 대한 설명"
}
  • ok: true -- 동작 허용
  • ok: false -- 동작 방지 (reason 필수)

Agent Hooks (type: "agent")

Prompt Hook과 유사하지만, 멀티턴 도구 접근이 가능한 서브에이전트를 생성합니다. Read, Grep, Glob 등의 도구를 사용하여 조건을 검증할 수 있습니다.

{
  "type": "agent",
  "prompt": "모든 단위 테스트가 통과하는지 확인하세요. 테스트 스위트를 실행하고 결과를 확인하세요. $ARGUMENTS",
  "timeout": 120
}
필드 필수 설명
type "agent"
prompt 검증 내용을 설명하는 프롬프트. $ARGUMENTS로 입력 JSON 주입
model 아니요 사용할 모델. 기본값: 빠른 모델
timeout 아니요 초 단위 타임아웃. 기본값: 60

Agent Hook은 최대 50턴까지 실행되며, Prompt Hook과 동일한 { "ok": true/false } 응답 형식을 사용합니다.

이벤트별 지원 타입

모든 이벤트가 세 가지 타입을 모두 지원하는 것은 아닙니다:

command, prompt, agent 모두 지원: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, UserPromptSubmit, Stop, SubagentStop, TaskCompleted

command만 지원: SessionStart, SessionEnd, Notification, SubagentStart, TeammateIdle, ConfigChange, PreCompact, WorktreeCreate, WorktreeRemove


Matcher 패턴

matcher 필드는 Hook이 실행되는 시점을 필터링하는 정규식 문자열입니다. "*", "", 또는 matcher를 생략하면 모든 발생에 매칭됩니다.

이벤트별 Matcher 대상

이벤트 Matcher가 필터링하는 대상 예시
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest 도구 이름 Bash, Edit|Write, mcp__.*
SessionStart 세션 시작 방식 startup, resume, clear, compact
SessionEnd 세션 종료 이유 clear, logout, prompt_input_exit, other
Notification 알림 유형 permission_prompt, idle_prompt, auth_success
SubagentStart, SubagentStop 에이전트 유형 Bash, Explore, Plan, 커스텀 에이전트명
PreCompact 압축 트리거 manual, auto
ConfigChange 설정 소스 user_settings, project_settings, local_settings
UserPromptSubmit, Stop, TeammateIdle, TaskCompleted, WorktreeCreate, WorktreeRemove 지원 안 함 항상 모든 발생 시 실행

Matcher는 정규식이므로 Edit|Write는 두 도구 중 하나에 매칭되고, Notebook.*은 Notebook으로 시작하는 모든 도구에 매칭됩니다.

MCP 도구 매칭

MCP 도구는 mcp__<서버>__<도구> 패턴을 따릅니다:

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

Decision Control (결정 제어)

이벤트마다 동작을 제어하는 방식이 다릅니다:

이벤트 결정 패턴 주요 필드
UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange 최상위 decision decision: "block", reason
TeammateIdle, TaskCompleted Exit code만 Exit 2가 동작 차단, stderr가 피드백으로 전달
PreToolUse hookSpecificOutput permissionDecision (allow/deny/ask), permissionDecisionReason
PermissionRequest hookSpecificOutput decision.behavior (allow/deny)
WorktreeCreate stdout 경로 생성된 워크트리의 절대 경로 출력
WorktreeRemove, Notification, SessionEnd, PreCompact 없음 결정 제어 없음. 로깅/정리 등 부수 효과용

최상위 decision 패턴

UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange에서 사용:

{
  "decision": "block",
  "reason": "테스트 스위트가 통과해야 진행할 수 있습니다"
}

PreToolUse 패턴

hookSpecificOutput으로 더 세밀한 제어가 가능합니다 -- allow, deny, 또는 사용자에게 확인 요청(ask):

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "데이터베이스 쓰기가 허용되지 않습니다"
  }
}

permissionDecision 값:

  • "allow": 권한 시스템 우회하여 허용
  • "deny": 도구 호출 방지
  • "ask": 사용자에게 확인 요청

추가 필드:

  • updatedInput: 실행 전 도구 입력 파라미터 수정
  • additionalContext: Claude 컨텍스트에 문자열 추가

PermissionRequest 패턴

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedInput": {
        "command": "npm run lint"
      }
    }
  }
}

실전 예제

예제 1: 파일 쓰기 후 자동 린트

파일이 작성되거나 편집된 후 자동으로 린트를 실행합니다.

.claude/settings.json:

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

.claude/hooks/lint-check.sh:

#!/bin/bash
# PostToolUse hook: 파일 쓰기/편집 후 린트 실행

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# TypeScript/JavaScript 파일만 처리
if [[ "$FILE_PATH" == *.ts || "$FILE_PATH" == *.tsx || "$FILE_PATH" == *.js || "$FILE_PATH" == *.jsx ]]; then
  LINT_OUTPUT=$(npx eslint "$FILE_PATH" 2>&1)
  if [ $? -ne 0 ]; then
    echo "{\"decision\": \"block\", \"reason\": \"린트 에러 발견:\\n$LINT_OUTPUT\"}"
  fi
fi

exit 0

예제 2: 위험한 명령 차단

rm -rf 같은 파괴적 명령을 사전에 차단합니다.

.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

.claude/hooks/block-rm.sh:

#!/bin/bash
# PreToolUse hook: rm -rf 명령 차단

COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "파괴적 명령이 Hook에 의해 차단되었습니다"
    }
  }'
else
  exit 0  # 명령 허용
fi

예제 3: 세션 시작 시 Git 컨텍스트 주입

세션이 시작될 때 현재 브랜치와 최근 커밋 정보를 Claude 컨텍스트에 추가합니다.

.claude/settings.json:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/git-context.sh"
          }
        ]
      }
    ]
  }
}

.claude/hooks/git-context.sh:

#!/bin/bash
# SessionStart hook: Git 컨텍스트 주입

BRANCH=$(git branch --show-current 2>/dev/null)
LAST_COMMITS=$(git log --oneline -5 2>/dev/null)
STATUS=$(git status --short 2>/dev/null)

jq -n --arg branch "$BRANCH" --arg commits "$LAST_COMMITS" --arg status "$STATUS" '{
  hookSpecificOutput: {
    hookEventName: "SessionStart",
    additionalContext: ("현재 브랜치: " + $branch + "\n최근 커밋:\n" + $commits + "\n변경된 파일:\n" + $status)
  }
}'

예제 4: 안전한 도구 자동 승인

읽기 전용 도구(Read, Glob, Grep)를 자동으로 승인합니다.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Glob|Grep",
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"permissionDecisionReason\":\"읽기 전용 도구 자동 승인\"}}'"
          }
        ]
      }
    ]
  }
}

예제 5: Prompt Hook으로 Stop 검증

LLM이 Claude의 작업 완료 여부를 평가합니다.

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Claude의 작업 완료 여부를 평가하세요. 컨텍스트: $ARGUMENTS\n\n다음을 확인하세요:\n1. 사용자가 요청한 모든 작업이 완료되었는가\n2. 해결해야 할 에러가 남아있는가\n3. 후속 작업이 필요한가\n\nJSON으로 응답하세요: {\"ok\": true}면 정지 허용, {\"ok\": false, \"reason\": \"설명\"}이면 작업 계속.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

예제 6: 태스크 완료 전 테스트 검증

태스크가 완료로 표시되기 전에 테스트가 통과하는지 확인합니다.

.claude/hooks/check-tests-on-complete.sh:

#!/bin/bash
# TaskCompleted hook: 태스크 완료 전 테스트 실행

INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

# 테스트 스위트 실행
if ! npm test 2>&1; then
  echo "테스트 미통과. 실패한 테스트를 수정한 후 완료하세요: $TASK_SUBJECT" >&2
  exit 2
fi

exit 0

고급 기능

비동기 Hook (async: true)

기본적으로 Hook은 Claude의 실행을 차단합니다. 오래 걸리는 작업(테스트, 배포, 외부 API 호출 등)은 "async": true로 백그라운드 실행할 수 있습니다.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",
            "async": true,
            "timeout": 300
          }
        ]
      }
    ]
  }
}

비동기 Hook의 특성:

  • type: "command" Hook만 지원 (prompt/agent Hook은 비동기 실행 불가)
  • 동작을 차단하거나 결정을 반환할 수 없음 (이미 진행됨)
  • Hook 출력은 다음 대화 턴에 전달됨
  • 각 실행이 별도 백그라운드 프로세스를 생성

.claude/hooks/run-tests-async.sh:

#!/bin/bash
# 비동기 PostToolUse hook: 파일 변경 후 테스트 실행

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# 소스 파일만 처리
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
  exit 0
fi

# 테스트 실행 및 결과 systemMessage로 보고
RESULT=$(npm test 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
  echo "{\"systemMessage\": \"$FILE_PATH 편집 후 테스트 통과\"}"
else
  echo "{\"systemMessage\": \"$FILE_PATH 편집 후 테스트 실패: $RESULT\"}"
fi

환경 변수 지속 (CLAUDE_ENV_FILE)

SessionStart Hook에서는 CLAUDE_ENV_FILE 환경 변수를 사용하여 세션 전체에 걸쳐 환경 변수를 설정할 수 있습니다.

#!/bin/bash
# SessionStart hook: 환경 변수 설정

if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
  echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
  echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi

exit 0

셋업 명령에 의한 환경 변경을 캡처하려면:

#!/bin/bash
# SessionStart hook: nvm 등 셋업 명령의 환경 변경 캡처

ENV_BEFORE=$(export -p | sort)

# 환경을 수정하는 셋업 명령 실행
source ~/.nvm/nvm.sh
nvm use 20

if [ -n "$CLAUDE_ENV_FILE" ]; then
  ENV_AFTER=$(export -p | sort)
  comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi

exit 0

CLAUDE_ENV_FILE에 기록된 변수는 세션의 모든 후속 Bash 명령에서 사용할 수 있습니다. 이 변수는 SessionStart Hook에서만 사용 가능합니다.

경로 변수

Hook 스크립트를 참조할 때 유용한 환경 변수:

  • $CLAUDE_PROJECT_DIR: 프로젝트 루트. 공백이 포함된 경로를 처리하려면 따옴표로 감싸세요.
  • ${CLAUDE_PLUGIN_ROOT}: 플러그인의 루트 디렉터리. 플러그인에 번들된 스크립트용.
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
          }
        ]
      }
    ]
  }
}

Skill/Agent 프론트매터에서의 Hook

설정 파일 외에도 Skill과 서브에이전트의 프론트매터에서 직접 Hook을 정의할 수 있습니다. 이 Hook은 해당 컴포넌트의 생명주기에 한정됩니다.

---
name: secure-operations
description: 보안 검사와 함께 작업 수행
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/security-check.sh"
---

서브에이전트에서 Stop Hook은 자동으로 SubagentStop으로 변환됩니다.

disableAllHooks

Hook을 제거하지 않고 일시적으로 비활성화하려면 설정 파일에 "disableAllHooks": true를 추가하거나, /hooks 메뉴의 토글을 사용하세요.

관리 설정 계층을 준수합니다: 관리자가 관리 정책 설정을 통해 구성한 Hook은 사용자/프로젝트/로컬 설정의 disableAllHooks로 비활성화할 수 없습니다.


보안 고려사항

Hook은 전체 사용자 권한으로 실행됩니다

Hook은 시스템 사용자의 전체 권한으로 셸 명령을 실행합니다. 사용자 계정이 접근할 수 있는 모든 파일을 수정, 삭제, 접근할 수 있습니다. 설정에 추가하기 전에 모든 Hook 명령을 검토하고 테스트하세요.

시작 시 스냅샷

Claude Code는 시작 시 Hook의 스냅샷을 캡처하고 세션 내내 사용합니다. 이는 악의적이거나 실수로 인한 Hook 수정이 검토 없이 세션 중간에 적용되는 것을 방지합니다. Hook이 외부에서 수정되면 Claude Code가 경고하고 /hooks 메뉴에서 검토한 후에야 변경이 적용됩니다.

보안 모범 사례

  • 입력 검증 및 정제: 입력 데이터를 맹목적으로 신뢰하지 마세요
  • 셸 변수에 항상 따옴표 사용: $VAR가 아닌 "$VAR" 사용
  • 경로 탐색 차단: 파일 경로에서 .. 확인
  • 절대 경로 사용: 스크립트에 전체 경로 지정, 프로젝트 루트에는 "$CLAUDE_PROJECT_DIR" 사용
  • 민감한 파일 건너뛰기: .env, .git/, 키 파일 등 처리 시 주의

트러블슈팅

Hook 디버깅

claude --debug로 실행하면 Hook 실행 세부 정보를 볼 수 있습니다:

[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: <명령> with timeout 600000ms
[DEBUG] Hook command completed with status 0: <stdout 출력>

Ctrl+O로 verbose 모드를 토글하면 트랜스크립트에서 Hook 진행 상황을 확인할 수 있습니다.

일반적인 문제

Hook이 실행되지 않음

  • Matcher 정규식이 올바른지 확인하세요
  • 설정 파일의 JSON 구문이 유효한지 확인하세요
  • claude --debug로 실행하여 매칭 과정을 확인하세요

JSON 파싱 실패

  • stdout에 JSON 객체만 출력되는지 확인하세요
  • 셸 프로필이 시작 시 텍스트를 출력하면 JSON 파싱을 방해할 수 있습니다
  • echo 대신 jq -n을 사용하면 유효한 JSON 생성에 도움됩니다

Stop Hook 무한 루프

  • stop_hook_active 필드를 확인하세요. 이 값이 true면 이미 Stop Hook으로 인해 계속 실행 중인 상태입니다
  • 트랜스크립트를 처리하여 무한 실행을 방지하세요

Hook 수정이 적용되지 않음

  • Claude Code는 시작 시 스냅샷을 캡처합니다
  • 세션 중 변경하면 /hooks 메뉴에서 검토가 필요합니다
  • 새 세션을 시작하면 변경 사항이 즉시 반영됩니다

요약

Hooks는 Claude Code의 생명주기 자동화 엔진입니다.

핵심 개념

  1. 설정: .claude/settings.json의 JSON 형식으로 정의
  2. 3가지 타입: Command (셸 명령), Prompt (LLM 평가), Agent (도구 접근 가능한 서브에이전트)
  3. 17개 이벤트: 세션 시작부터 종료까지 전체 생명주기 커버
  4. Matcher: 정규식으로 특정 도구/상황에만 Hook 실행
  5. Exit Code: 0(성공/JSON), 2(차단), 기타(비차단 에러)

다음 단계


(출처: Anthropic 공식 문서 - Hooks reference)

관련 가이드

Hooks 시스템 | Claude Code 가이드 | GodDaeHee | GodDaeHee