Hooks 시스템
읽는 시간: 14분 | 난이도: 고급자
Claude Code의 Hooks를 사용하면 세션 생명주기의 특정 시점에서 셸 명령, LLM 프롬프트, 에이전트를 자동으로 실행할 수 있습니다.
Hooks란?
Hooks는 Claude Code의 생명주기 특정 시점에서 자동 실행되는 사용자 정의 핸들러입니다. 파일을 쓸 때 자동으로 린트를 실행하거나, 위험한 명령을 차단하거나, 세션 시작 시 개발 컨텍스트를 주입하는 등의 자동화를 구현할 수 있습니다.
Hook 생명주기
Hook이 실행되는 흐름은 다음과 같습니다:
이벤트 발생 → Matcher 확인 → Hook 핸들러 실행 → Claude Code가 결과에 따라 동작
예를 들어, PreToolUse 이벤트에 Bash matcher를 설정하면:
- Claude가 Bash 도구를 호출하려 할 때
PreToolUse이벤트 발생 - Matcher
"Bash"가 도구 이름과 일치하는지 확인 - 일치하면 Hook 핸들러(셸 스크립트 등)가 실행됨
- 핸들러의 exit code와 stdout JSON에 따라 Claude Code가 허용/차단/수정
설정 방법
Hooks는 JSON 설정 파일에 정의합니다. 3단계 중첩 구조로 되어 있습니다:
- 이벤트: 어떤 시점에 반응할지 (
PreToolUse,Stop등) - Matcher 그룹: 언제 실행할지 필터링 ("Bash 도구에만" 등)
- 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)에서만 표시. UserPromptSubmit과 SessionStart는 stdout이 Claude 컨텍스트에 추가됨 |
| 2 | 차단 에러 | stdout/JSON 무시. stderr 텍스트가 Claude에게 에러 메시지로 전달 |
| 기타 | 비차단 에러 | stderr가 verbose 모드에서 표시되고 실행 계속 |
stdout JSON 출력
Exit 0으로 종료하면서 JSON을 stdout에 출력하여 세밀한 제어가 가능합니다:
| 필드 | 기본값 | 설명 |
|---|---|---|
continue |
true |
false면 Claude가 완전히 처리를 중단 |
stopReason |
없음 | continue가 false일 때 사용자에게 표시할 메시지 |
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의 생명주기 자동화 엔진입니다.
핵심 개념
- 설정:
.claude/settings.json의 JSON 형식으로 정의 - 3가지 타입: Command (셸 명령), Prompt (LLM 평가), Agent (도구 접근 가능한 서브에이전트)
- 17개 이벤트: 세션 시작부터 종료까지 전체 생명주기 커버
- Matcher: 정규식으로 특정 도구/상황에만 Hook 실행
- Exit Code: 0(성공/JSON), 2(차단), 기타(비차단 에러)
