Quick Flow
hook은 Claude Code lifecycle의 정해진 시점에 command, HTTP endpoint, prompt, agent, MCP tool을 실행합니다. 실행 전 보호는 PreToolUse, 성공한 도구 뒤의 포맷·검사는 PostToolUse, 실패 뒤의 처리에는 PostToolUseFailure를 고릅니다. command hook의 입력은 인자가 아니라 stdin JSON입니다.
도구 호출 전 -> PreToolUse -> allow / deny / ask / defer
도구 성공 뒤 -> PostToolUse -> formatter, lint, 추가 맥락
도구 실패 뒤 -> PostToolUseFailure -> 실패 기록, 복구 안내
응답 종료 전 -> Stop -> 완료 조건 재검사{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}]
}]
}
}이벤트와 matcher
PreToolUse와 PostToolUse는 agent loop 안의 각 도구 호출마다 발생합니다. PreToolUse는 실행 전에 차단·승인·입력 변경을 결정할 수 있고, PostToolUse는 도구가 성공한 뒤에만 실행됩니다. 실패한 도구 결과를 다룰 때 PostToolUse를 붙이면 실행되지 않으므로 PostToolUseFailure를 사용합니다.
| 목적 | 이벤트 | 이미 일어난 일 |
|---|---|---|
| 위험한 명령·경로 차단 | PreToolUse | 아직 없음 |
| 편집 후 formatter·lint | PostToolUse | 도구가 성공함 |
| 실패 로그와 복구 안내 | PostToolUseFailure | 도구가 실패함 |
| 세션 시작 맥락 주입 | SessionStart | 세션 시작 |
| 완료 조건을 못 채우면 계속 작업 | Stop | 응답이 끝나려 함 |
matcher는 이벤트마다 다른 필드를 고릅니다. tool event에서는 tool 이름을 대상으로 하므로 Edit|Write, Bash|PowerShell, mcp__memory__.*처럼 좁힙니다. matcher를 생략하거나 "*"를 쓰면 해당 이벤트의 모든 호출에 실행됩니다. 명령 인자까지 좁혀야 하면 handler의 if에 Bash(git *), Edit(*.ts) 같은 permission-rule pattern을 둡니다.
stdin과 차단 결정
command hook은 JSON을 stdin으로 받습니다. PreToolUse의 Bash 호출은 tool_input.command, 파일 도구는 tool_input.file_path처럼 tool마다 입력 표면이 다릅니다. $1은 이 JSON이 아니므로 $1을 검사하는 script는 실제 명령을 보지 못합니다.
#!/usr/bin/env bash
# .claude/hooks/block-rm.sh
command=$(jq -r '.tool_input.command // empty')
if [[ "$command" == *"rm -rf"* ]]; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by project policy"
}
}'
else
exit 0 # 결정 없음: 일반 permission 흐름을 계속 사용
fi차단 방식은 둘 중 하나만 사용합니다.
- exit code 방식: stderr에 이유를 쓰고
exit 2로 끝냅니다.PreToolUse에서는 도구 호출이 차단됩니다.exit 1은 대부분 비차단 오류라서 정책 차단에 맞지 않습니다. - JSON decision 방식:
exit 0으로 끝내고 stdout에 JSON만 출력합니다.permissionDecision은allow,deny,ask,defer중 하나입니다. exit 2와 JSON을 함께 쓰면 JSON은 처리되지 않습니다.
allow도 모든 상황을 무조건 통과시키지 않습니다. 사용자 상호작용이 필요한 도구, 조직의 deny/ask 규칙은 여전히 더 강한 경계로 적용됩니다. 파일 경로는 hook에 도달할 때 절대 경로이고 Windows에서는 backslash로 올 수 있으므로, 경로 정책을 비교할 때는 구분자를 정규화합니다.
설정 범위와 성능
공유할 프로젝트 정책은 .claude/settings.json, 개인 장비별 알림·실험은 .claude/settings.local.json, 개인의 모든 프로젝트에 적용할 규칙은 ~/.claude/settings.json에 둡니다. 웹의 cloud session은 로컬 ~/.claude/settings.json을 읽지 않으므로, cloud에서도 필요한 정책은 repository 또는 조직 관리 설정에 둡니다.
Hook 설정은 서로 다른 범위에서 병합됩니다. 개인 hook이 프로젝트 hook을 대체하지 않으므로, 같은 formatter나 차단 규칙이 중복 실행되지 않게 source를 분리합니다. 매 도구 호출마다 실행되는 command는 짧아야 하며, 느린 test suite는 PostToolUse에 동기로 매달기보다 명시적 검증 단계나 비동기 hook을 검토합니다.
자주 틀리는 부분
PostToolUse에서 차단 결정을 내리더라도 파일 쓰기·명령 실행·네트워크 요청은 이미 끝났습니다. side effect를 막는 규칙은 반드시 PreToolUse 또는 서비스 자체의 권한 정책에 둡니다.
- stdout에 debug log를 섞어 JSON decision을 망가뜨리지 않습니다. 구조화된 출력을 쓸 때 stdout은 JSON 객체 하나만 남깁니다.
Bash만 matcher로 두면 Windows에서 primary shell인PowerShell호출은 보호하지 못할 수 있습니다. 두 shell을 지원해야 하면Bash|PowerShell과 각 도구에 맞는if조건을 둡니다.Readhook은 prompt의@파일첨부에는 실행되지 않습니다. 해당 경로를 막아야 하면 Read deny rule을 별도로 설정합니다./hooks메뉴에서 event, matcher, handler, 설정 파일 출처를 확인해 실제로 어느 hook이 동작하는지 먼저 진단합니다.
참고 링크
2 sources