Claude Code Hooks 메커니즘 완전 분석. 5가지 이벤트 유형부터 종료 코드 제어까지, 실전 설정 4가지로 AI 작업을 감사·차단·자동화하는 방법.
Claude Code가 파일을 읽고, 코드를 작성하고, 명령을 실행할 때마다 백그라운드에서 이벤트 시스템이 작동합니다. Hooks는 이 시스템에 접속하는 인터페이스입니다——원하는 시점에 자신의 로직을 주입하고, 자동으로 코드 검사를 실행하고, 로그를 기록하고, 위험한 작업을 차단하고, 어떤 셸 명령이든 트리거할 수 있습니다.
이 글에서는 Hooks의 메커니즘, 설정 방법, 실전 활용법을 완전히 설명합니다.
Hooks는 settings.json에 설정된 셸 명령으로, 특정 이벤트가 발생할 때 Claude Code가 자동으로 실행합니다.
가장 직관적인 비유: git hooks입니다. git은 commit, push 등의 작업 전후에 스크립트를 트리거할 수 있습니다. Claude Code Hooks도 완전히 같은 방식이며, 차이점은 트리거 시점이 AI의 도구 호출이라는 것입니다.
왜 중요한가?
Claude Code의 능력이 강해질수록 결정론적인 제어 레이어가 더 필요합니다. Hooks가 제공하는 것:
- 프롬프트에 의존하지 않는 실행 보장 (Claude는 지시를 무시할 수 있지만 hook은 반드시 실행됨)
- 감사 가능한 작업 기록
- 자동화된 품질 검사
| 유형 | 트리거 시점 | 주요 용도 |
|---|---|---|
PreToolUse |
도구 호출 전 | 위험한 작업 차단, 의도 기록 |
PostToolUse |
도구 호출 후 | 자동 lint, 테스트 실행 |
PreCompact |
컨텍스트 압축 전 | 현재 상태 스냅샷 저장 |
Notification |
Claude가 알림을 보낼 때 | 데스크톱 알림, Slack 메시지 |
Stop |
Claude가 응답을 완료했을 때 | 로그 집계, 후속 플로우 트리거 |
가장 자주 쓰이는 것은 PreToolUse와 PostToolUse로, 도구 호출을 가로채고 후처리하는 데 사용합니다.
Hooks는 ~/.claude/settings.json(전역) 또는 프로젝트 루트의 .claude/settings.json(프로젝트 단위)에 작성합니다:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm run lint --silent"
}
]
}
]
}
}
세 가지 핵심 필드:
matcher: 도구 이름과 매칭하는 정규식. 어떤 도구 호출 후에 이 hook이 트리거될지 결정합니다. "Write|Edit"은 파일 쓰기 또는 편집 시 트리거됩니다. 비워두거나 ".*"로 모든 도구와 매칭합니다.type: 현재는 "command"만 지원.command: 임의의 셸 명령.matcher를 작성할 때 필요한 도구 이름:
| 도구 이름 | 해당 작업 |
|---|---|
Write |
새 파일 쓰기 |
Edit |
파일 편집 |
Bash |
셸 명령 실행 |
Read |
파일 읽기 |
Glob |
파일 검색 |
Grep |
내용 검색 |
TodoWrite |
작업 목록 업데이트 |
실행 시 stdin을 통해 JSON이 전달됩니다:
{
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.rb",
"content": "..."
},
"tool_response": "..."
}
PreToolUse는 tool_input(호출 전 인수)을 받고, PostToolUse는 tool_response(도구 반환값)도 함께 받습니다.
조건 로직이 있는 hook 예시:
#!/bin/bash
input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path')
# .rb 파일에만 rubocop 실행
if [[ "$file" == *.rb ]]; then
rubocop "$file" --autocorrect-all --no-color
fi
Hook의 종료 코드가 Claude Code의 후속 동작을 제어합니다:
| 종료 코드 | 의미 |
|---|---|
0 |
성공, 실행 계속 |
2 |
차단: 현재 도구 호출을 취소하고 stderr 출력을 Claude에 피드백 |
| 기타 비제로 | 오류 기록 후 실행 계속 |
종료 코드 2가 가장 유용합니다——PreToolUse에서 차단 로직을 작성해 Claude의 작업을 막고 이유를 전달할 수 있습니다.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.rb ]] && rubocop -A \"$file\" --no-color -q || true'"
}
]
}
]
}
}
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'cmd=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .command); if echo \"$cmd\" | grep -qE \"rm.*/(migrations|seeds)\"; then echo \"migrations 또는 seeds 디렉토리 삭제는 금지되어 있습니다\" >&2; exit 2; fi'"
}
]
}
]
}
}
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|Bash",
"hooks": [
{
"type": "command",
"command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') $CLAUDE_TOOL_NAME: $(echo $CLAUDE_TOOL_INPUT | jq -c .)\" >> ~/.claude/audit.log"
}
]
}
]
}
}
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude가 완료되었습니다\" with title \"Claude Code\"'"
}
]
}
]
}
}
Hooks를 배치할 수 있는 두 곳:
전역(~/.claude/settings.json): 모든 프로젝트에 공통으로 적용되는 규칙 (로그, 알림 등).
프로젝트 단위(.claude/settings.json): 프로젝트 고유의 검사. 프로젝트 단위 hooks와 전역 hooks는 병합되어 함께 실행됩니다.
팀 프로젝트에서는 프로젝트 단위 settings.json을 git에 커밋해 모든 팀원이 동일한 hooks를 실행하도록 권장합니다.
Hook이 작동하지 않을 때 확인 순서:
write와 Write는 다릅니다echo "hook triggered" >> /tmp/hook.log로 트리거 확인 후 복잡한 로직 추가Hooks의 핵심 가치는 예측 불가능한 AI 동작과 결정론적인 엔지니어링 표준을 연결하는 것입니다. Claude는 테스트 실행을 잊을 수 있지만 PostToolUse hook은 잊지 않습니다. Claude가 실수로 파일을 삭제하려 해도 PreToolUse 차단기가 막아줍니다.
hook 하나부터 시작하세요: 파일 쓰기 후 자동 lint. 그게 잘 돌아가면 나머지를 추가하면 됩니다.