Hooks 시스템
Hooks는 Claude Code의 라이프사이클 특정 시점에서 셸 명령어, LLM 프롬프트, 또는 에이전트를 자동으로 실행할 수 있게 해주는 시스템입니다. 테스트 자동 실행, 포맷팅, 알림, 로깅, 보안 검증 등 반복 작업을 코딩 워크플로우에 내장할 수 있습니다.
Hooks란 무엇인가
Claude Code가 파일을 수정하거나 명령을 실행할 때, 그 동작에 "끼어들어" 추가 작업을 수행할 수 있습니다.
flowchart TD
Start(("도구 실행 시도\n(Tool Use)")) --> Pre{"[PreToolUse Hook]"}
Pre -- "allow / 통과" --> Exec["🛠️ 실제 도구 실행\n(파일 쓰기, Bash)"]
Pre -- "deny / 차단" --> Abort["🛑 실행 중단\n(이유를 Claude에게 반환)"]
Exec --> Success{"성공 여부 판단"}
Success -- "성공(0)" --> PostSuccess{"[PostToolUse Hook]"}
Success -- "실패(비정상 종료)" --> PostFail{"[PostToolUseFailure Hook]"}
PostSuccess --> Async["비동기(Async) Hook?\n백그라운드 포맷팅 등"]
PostFail --> Async
Async --> End(("다음 작업 진행\n(Claude에게 결과 전달)"))
Abort --> End
style Pre fill:#ffe3e3,stroke:#ff8787
style PostSuccess fill:#d3f9d8,stroke:#51cf66
style PostFail fill:#fff3cd,stroke:#ffe066
style Exec fill:#e7f5ff,stroke:#74c0fc
18가지 Hook 이벤트
| Hook 이벤트 | 실행 시점 | 차단 가능 |
|---|---|---|
SessionStart | 세션 시작 또는 재개 시 | - |
UserPromptSubmit | 사용자 프롬프트 제출 시 (처리 전) | O |
InstructionsLoaded | CLAUDE.md 또는 .claude/rules/*.md 로드 시 | - |
PreToolUse | 도구 실행 직전 | O |
PermissionRequest | 권한 대화상자 표시 시 | O |
PostToolUse | 도구 실행 성공 후 | - |
PostToolUseFailure | 도구 실행 실패 후 | - |
Notification | 알림 발송 시 | - |
SubagentStart | 서브에이전트 생성 시 | - |
SubagentStop | 서브에이전트 완료 시 | O |
Stop | Claude 응답 완료 시 | O |
TeammateIdle | Agent Teams 팀원이 유휴 전환 시 | O |
TaskCompleted | 태스크 완료 마킹 시 | O |
ConfigChange | 설정 파일 변경 시 | O |
WorktreeCreate | 워크트리 생성 시 | O |
WorktreeRemove | 워크트리 제거 시 | - |
PreCompact | 컨텍스트 압축 직전 | - |
SessionEnd | 세션 종료 시 | - |
4가지 Hook 유형
Command Hook (셸 명령)
가장 기본적인 유형입니다. 셸 명령을 실행하고 stdin으로 JSON 입력을 받습니다.
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_PROJECT_DIR/src\""
}
Prompt Hook (LLM 평가)
Claude 모델에 프롬프트를 보내 yes/no 결정을 받습니다. 코드를 작성하지 않고도 지능적인 검증이 가능합니다.
{
"type": "prompt",
"prompt": "이 변경이 모든 태스크를 완료했는지 평가해줘: $ARGUMENTS",
"timeout": 30
}
LLM은 {"ok": true} 또는 {"ok": false, "reason": "이유"} 형식으로 응답합니다.
v2.1.294에서 Prompt/Agent Hook의 지시가 차단해야 할 동작을 허용할 수 있던 문제와 Stop·SubagentStop 판정 관련 수정이 포함됐습니다. Hook을 보안 경계로 사용할 때는 Claude Code를 최신 상태로 유지하고, 허용·차단 양쪽 사례를 실제 환경에서 테스트하세요. Hook은 권한 정책이나 샌드박스를 대신하지 않습니다. 자세한 변경은 공식 변경 기록을 확인하세요.
Agent Hook (에이전트 검증)
서브에이전트를 생성하여 파일 읽기, 코드 검색 등 도구를 사용한 다단계 검증을 수행합니다.
{
"type": "agent",
"prompt": "모든 단위 테스트가 통과하는지 테스트 스위트를 실행하고 결과를 확인해줘. $ARGUMENTS",
"timeout": 120
}
HTTP Hook (외부 엔드포인트)
외부 HTTP 엔드포인트로 직접 POST 요청을 보냅니다. Slack webhook, 모니터링 서비스, 로깅 시스템 등과 연동할 때 셸 스크립트 없이 바로 연결할 수 있습니다.
{
"type": "http",
"url": "https://hooks.slack.com/services/T00/B00/xxxxx",
"timeout": 30
}
Hook 이벤트의 JSON 입력이 HTTP POST body로 전송됩니다.
Prompt/Agent Hook은 PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, UserPromptSubmit, Stop, SubagentStop, TaskCompleted에서만 사용 가능합니다. 나머지 이벤트는 Command/HTTP Hook만 지원합니다.
onFailure, v2.1.295+)Command/HTTP Hook에 onFailure: "block"을 지정하면 Hook을 시작할 수 없거나 timeout이 발생하거나 예상 밖의 종료 코드를 반환했을 때 작업이 통과하지 않고 차단됩니다. 이 동작은 해당 Hook의 실패 처리이며 권한 정책이나 샌드박스를 대신하는 보안 보장은 아닙니다. v2.1.295 변경 기록
설정 방법
Hooks는 settings.json에서 3단계 중첩 구조로 설정합니다:
- Hook 이벤트 선택 (예:
PostToolUse) - Matcher 패턴으로 필터링 (예: "Write|Edit 도구만")
- Hook 핸들러 정의 (실행할 명령/프롬프트)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_PROJECT_DIR/src\""
}
]
}
]
}
}
Hook 설정 위치
| 위치 | 범위 | 공유 |
|---|---|---|
~/.claude/settings.json | 모든 프로젝트 | 로컬 머신만 |
.claude/settings.json | 단일 프로젝트 | git 커밋 가능 |
.claude/settings.local.json | 단일 프로젝트 | gitignored |
| 관리형 정책 | 조직 전체 | 관리자 제어 |
플러그인 hooks/hooks.json | 플러그인 활성 시 | 플러그인에 번들 |
| Skills/Agent frontmatter | 컴포넌트 활성 시 | 컴포넌트 파일 내 |
/hooks 인터랙티브 메뉴
/hooks
설정 파일을 직접 편집하지 않고도 Hook을 확인·추가·삭제할 수 있습니다. 각 Hook에는 소스가 표시됩니다: [User], [Project], [Local], [Plugin].
Matcher 패턴
matcher는 정규식으로, 어떤 조건에서 Hook이 실행될지 결정합니다. "*", "", 또는 생략하면 모든 경우에 실행됩니다. 하이픈이 포함된 식별자(예: code-reviewer)는 부분 일치가 아니라 정확히 일치해야 매칭됩니다 (v2.1.195+).
이벤트별 매칭 대상
| 이벤트 | 매칭 대상 | 예시 |
|---|---|---|
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest | 도구 이름 | Bash, Edit|Write, mcp__.* |
SessionStart | 세션 시작 방법 | startup, resume, clear, compact |
SessionEnd | 종료 이유 | clear, logout, prompt_input_exit, bypass_permissions_disabled, other |
Notification | 알림 유형 | permission_prompt, idle_prompt, auth_success, elicitation_dialog, agent_needs_input·agent_completed (claude agents 백그라운드 세션, v2.1.198+) |
SubagentStart, SubagentStop | 에이전트 유형 | Bash, Explore, Plan, 커스텀 에이전트 이름 |
ConfigChange | 설정 소스 | user_settings, project_settings, local_settings, policy_settings, skills |
PreCompact | 트리거 방식 | manual, auto |
UserPromptSubmit, Stop, TeammateIdle, TaskCompleted, WorktreeCreate, WorktreeRemove, InstructionsLoaded | 매칭 미지원 | 항상 실행 |
MCP 도구 매칭
MCP 도구는 mcp__<서버>__<도구> 형식으로 일반 도구와 동일하게 매칭합니다:
"matcher": "mcp__memory__.*" // memory 서버의 모든 도구
"matcher": "mcp__.*__write.*" // 모든 서버의 write 관련 도구
"matcher": "mcp__brave-search__.*" // 하이픈 포함 서버의 모든 도구
하이픈이 포함된 서버 이름은 부분 일치되지 않으므로, 해당 서버의 모든 도구를 잡으려면 mcp__brave-search__.*처럼 .*까지 명시해야 합니다 (v2.1.195+).
JSON 입출력
Hook은 stdin으로 JSON 입력을 받고, stdout으로 JSON 결과를 반환합니다.
공통 입력 필드
모든 이벤트가 받는 기본 필드:
| 필드 | 설명 |
|---|---|
session_id | 세션 ID |
transcript_path | 대화 트랜스크립트 경로 |
cwd | 현재 작업 디렉토리 |
permission_mode | 현재 권한 모드 |
hook_event_name | 발생한 이벤트 이름 |
PreToolUse 예시:
{
"session_id": "abc123",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "npm test" }
}
종료 코드
| 종료 코드 | 의미 |
|---|---|
0 | 성공 — stdout의 JSON이 처리됨 |
2 | 차단 — stderr 메시지가 Claude에게 전달됨 |
| 기타 | 비차단 에러 — 실행 계속 |
JSON 출력 필드
{
"continue": false,
"stopReason": "빌드 실패. 에러 수정 후 계속하세요",
"suppressOutput": false,
"systemMessage": "사용자에게 보여줄 경고"
}
| 필드 | 설명 |
|---|---|
continue | false면 Claude 실행 완전 중단 |
stopReason | continue: false 시 사용자에게 표시 |
suppressOutput | true면 verbose 출력 숨김 |
systemMessage | 사용자에게 표시할 경고 |
주요 이벤트 상세
SessionStart
세션 시작 시 실행됩니다. 개발 컨텍스트 로딩, 환경변수 설정에 유용합니다.
CLAUDE_ENV_FILE — 환경변수 영속화
SessionStart Hook에서만 사용 가능한 CLAUDE_ENV_FILE 환경변수로, Hook에서 설정한 환경변수를 세션 내 모든 Bash 명령에서 사용할 수 있습니다:
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi
exit 0
컨텍스트 주입
stdout에 출력하거나 JSON의 additionalContext 필드로 Claude에게 추가 컨텍스트를 제공할 수 있습니다:
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "현재 이슈: #42, 마지막 배포: 2시간 전"
}
}
PreToolUse
도구 실행 전에 실행됩니다. allow, deny, ask 결정이 가능합니다:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "rm -rf 명령은 Hook에 의해 차단됨"
}
}
| 결정 | 의미 |
|---|---|
allow | 권한 시스템 우회, 바로 실행 |
deny | 도구 호출 차단, 이유를 Claude에게 전달 |
ask | 사용자에게 확인 프롬프트 표시 |
updatedInput으로 도구 입력을 수정할 수도 있습니다:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"updatedInput": { "command": "npm run lint -- --fix" }
}
}
PostToolUse
도구 실행 성공 후 실행됩니다. 도구 결과에 대한 피드백을 Claude에게 제공할 수 있습니다.
{
"decision": "block",
"reason": "린트 에러 발견. 수정 후 계속하세요"
}
Stop
Claude 응답 완료 시 실행됩니다. decision: "block"으로 Claude가 멈추지 않고 계속 작업하게 할 수 있습니다.
{
"decision": "block",
"reason": "테스트가 아직 실패합니다. 모든 테스트를 통과시킨 후 종료하세요"
}
Stop Hook은 stop_hook_active 필드를 확인하세요. 이미 Stop Hook에 의해 계속 실행 중이면 true입니다. 이를 무시하면 Claude가 무한히 실행될 수 있습니다.
SubagentStart / SubagentStop
서브에이전트의 생성과 완료를 감지합니다. matcher로 에이전트 유형 필터링이 가능합니다:
{
"hooks": {
"SubagentStart": [
{
"matcher": "Explore",
"hooks": [{
"type": "command",
"command": "echo 'Explore 에이전트 시작' >> ~/agent-log.txt"
}]
}
]
}
}
TeammateIdle / TaskCompleted
Agent Teams에서 팀원이 유휴 전환되거나 태스크가 완료될 때 실행됩니다. 품질 게이트로 활용할 수 있습니다:
#!/bin/bash
# TaskCompleted Hook — 테스트 통과 시에만 태스크 완료 허용
if ! npm test 2>&1; then
echo "테스트 실패. 테스트를 수정한 후 완료하세요." >&2
exit 2
fi
exit 0
WorktreeCreate / WorktreeRemove
--worktree 또는 isolation: "worktree" 사용 시 실행됩니다. Git 외 VCS(SVN, Perforce 등)를 사용하는 경우, 기본 git 워크트리 동작을 대체할 수 있습니다.
ConfigChange
세션 중 설정 파일이 변경될 때 실행됩니다. 보안 감사 로깅이나 비인가 설정 변경 차단에 유용합니다.
비동기 Hook (Async)
"async": true로 설정하면 Hook이 백그라운드에서 실행되어 Claude를 차단하지 않습니다:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",
"async": true,
"timeout": 300
}
]
}
]
}
}
비동기 Hook은 결과가 다음 대화 턴에 전달됩니다. 의사결정(block/allow)은 불가능합니다. Command Hook만 지원합니다.
Skills/Agent에서의 Hook
Skills와 서브에이전트의 YAML frontmatter에서 직접 Hook을 정의할 수 있습니다. 해당 컴포넌트가 활성화된 동안만 실행됩니다:
---
name: secure-operations
description: 보안 검증이 포함된 작업 수행
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
---
환경변수 레퍼런스
| 변수명 | 설명 |
|---|---|
$CLAUDE_PROJECT_DIR | 프로젝트 루트 경로 |
${CLAUDE_PLUGIN_ROOT} | 플러그인 루트 디렉토리 |
$CLAUDE_ENV_FILE | 환경변수 영속화 파일 (SessionStart만) |
$CLAUDE_CODE_REMOTE | 원격 웹 환경에서 "true" |
Hook 스크립트를 프로젝트에 포함할 때는 $CLAUDE_PROJECT_DIR를 사용하면 작업 디렉토리와 무관하게 올바른 경로를 참조합니다:
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
실전 예제
파일 저장 시 자동 포맷팅
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$(cat | jq -r '.tool_input.file_path')\" 2>/dev/null"
}
]
}
]
}
}
위험한 명령 차단
#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "rm -rf 명령은 Hook에 의해 차단되었습니다"
}
}'
else
exit 0
fi
작업 완료 시 데스크톱 알림 (macOS)
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude 작업 완료\" with title \"Claude Code\"'"
}
]
}
]
}
}
작업 완료 시 데스크톱 알림 (Windows)
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "powershell -Command \"[System.Windows.Forms.MessageBox]::Show('Claude 작업 완료')\""
}
]
}
]
}
}
Prompt Hook으로 Stop 검증
코드 작성 없이 LLM으로 태스크 완료 여부를 평가:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "대화를 분석하여 다음을 판단해줘: 1) 모든 요청 태스크가 완료됐는지 2) 해결되지 않은 에러가 있는지 3) 후속 작업이 필요한지. $ARGUMENTS",
"timeout": 30
}
]
}
]
}
}
권장 Hook 구성
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$(cat | jq -r '.tool_input.file_path')\" 2>/dev/null; npx eslint \"$(cat | jq -r '.tool_input.file_path')\" --fix --quiet 2>&1 | head -10"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "echo \"[$(date '+%H:%M:%S')] 작업 완료\" >> ~/.claude-sessions.log"
}
]
}
]
}
}
주의사항
Prompt Hook과 Agent Hook은 서브에이전트를 포함해 세션당 동시에 최대 24개까지 실행됩니다. 이벤트 timeout을 넘겨 대기 중인 Hook은 최대 12개까지 허용되며, timeout을 1초보다 짧게 지정해도 1초로 처리됩니다. 다수의 Hook을 병렬 등록할 때는 이 한도와 이벤트별 timeout을 고려하세요.
Hook은 시스템 사용자의 전체 권한으로 실행됩니다.
- 셸 변수는 항상 따옴표로 감싸세요 (
"$VAR") - 파일 경로에서
..(경로 탐색)를 차단하세요 .env,.git/, 키 파일 같은 민감 파일을 피하세요- 신뢰할 수 없는 프로젝트의
.claude/settings.json을 그대로 적용하지 마세요
Hook 명령이 오래 걸리면 Claude Code 응답이 느려집니다. 긴 작업은 "async": true로 백그라운드 실행하세요.
claude --debug로 Hook 실행 상세 로그를 확인할 수 있습니다. Ctrl+O로 verbose 모드를 토글하면 트랜스크립트에서 Hook 진행 상황을 볼 수 있습니다.
이 챕터를 완료하셨나요?
학습 진도를 체크하여 나의 로드맵 달성률을 높여보세요.