클로드 코드 훅 설정 — rm -rf 차단부터 저장 후 자동 검사까지
클로드 코드 훅(hooks)은 settings.json의 hooks 항목에 "언제(이벤트) · 무엇에(matcher) · 어떤 명령을 실행할지"를 적는 것이 전부입니다. 프롬프트로 "위험한 명령은 실행하지 마"라고 부탁하는 것과 달리, 훅은 모델이 판단하지 않고 무조건 실행됩니다. 그래서 rm -rf 차단이나 저장 후 자동 포맷처럼 "반드시 되어야 하는 일"에 씁니다. 이 글은 Claude Code v2.1.204에서 훅 세 종류를 실제로 돌려 본 결과와 함께 설정법을 정리합니다.
1. 훅이 프롬프트보다 확실한 이유
CLAUDE.md에 "rm -rf는 쓰지 마라"라고 적어도 그건 모델에게 주는 부탁입니다. 대개 지켜지지만 반드시는 아닙니다. 훅은 클로드 코드가 도구를 실행하기 직전에 끼어들어 셸 명령을 돌리고, 그 명령이 "막아라"라고 답하면 도구 호출 자체가 취소됩니다. 모델의 협조가 필요 없습니다.
자주 쓰는 이벤트는 이 정도입니다. (전체 목록은 공식 문서에 30개 가까이 있습니다)
| 이벤트 | 언제 실행되나 | 막을 수 있나 |
|---|---|---|
PreToolUse |
도구를 실행하기 직전 | ✅ 도구 호출을 막는다 |
PostToolUse |
도구가 성공한 직후 | ❌ 이미 실행됨 |
UserPromptSubmit |
내가 보낸 말이 처리되기 전 | ✅ 처리를 막는다 |
SessionStart |
세션이 시작될 때 한 번 | ❌ 문맥 주입용 |
Stop |
클로드가 답변을 마쳤을 때 | ✅ 못 멈추게 한다 |
Notification |
알림이 발생할 때 | ❌ 알림 연동용 |
2. 클로드 코드 훅 설정 — 어느 파일에 쓰나
훅은 설정 파일의 hooks 키 아래에 적습니다. 파일 위치가 적용 범위를 정합니다.
| 경로 | 범위 | 팀 공유 |
|---|---|---|
~/.claude/settings.json |
내 모든 프로젝트 | ✕ |
.claude/settings.json |
이 저장소 | ○ (커밋하면 공유) |
.claude/settings.local.json |
이 저장소, 나만 | ✕ |
구조는 이벤트 → matcher 묶음 → 실행할 훅 목록 3단입니다. 아래가 실제로 동작을 확인한 최소 설정입니다.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh\"",
"timeout": 30
}
]
}
]
}
}
matcher는 도구 이름과 맞춰 봅니다. "Bash"처럼 정확히 쓰거나 "Edit|Write"처럼 |로 나열할 수 있고, 특수문자가 섞이면 정규식으로 해석됩니다("mcp__memory__.*"). 생략하거나 "*"로 두면 모든 도구에 걸립니다.
${CLAUDE_PROJECT_DIR}는 프로젝트 루트로 바뀌는 자리표시자입니다. 상대 경로로 적으면 클로드가 작업 폴더를 옮겼을 때 훅이 사라지니, 훅 스크립트 경로는 이 자리표시자로 적으세요.
3. 훅 스크립트는 표준 입력으로 JSON을 받는다
훅으로 지정한 명령은 표준 입력(stdin)으로 상황 정보가 담긴 JSON을 받습니다. 아래는 PostToolUse 훅이 실제로 받은 입력을 그대로 옮긴 것입니다(경로만 줄였습니다).
{
"session_id": "75b6fcd3-5b19-4ba0-9760-e53901fb4dbb",
"transcript_path": "C:\\Users\\...\\75b6fcd3-....jsonl",
"cwd": "C:\\...\\hooktest",
"prompt_id": "280011d1-6816-41ac-8094-daa29ec9cdb2",
"permission_mode": "default",
"effort": { "level": "high" },
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "C:\\...\\hooktest\\memo.txt",
"content": "hi"
}
}
여기서 중요한 게 tool_input입니다. Bash 도구라면 tool_input.command에 실행하려는 명령 문자열이 통째로 들어 있어서, 그 문자열을 검사해 막을지 말지 정하면 됩니다.
4. 방법 ① — 종료 코드 2로 막기 (제일 간단)
훅 스크립트가 종료 코드 2로 끝나면 도구 호출이 막히고, 그때 stderr에 쓴 내용이 차단 사유로 클로드에게 전달됩니다. 코드 0이면 통과, 그 외 코드는 사용자에게 경고만 띄우고 그냥 진행합니다.
#!/bin/bash
# .claude/hooks/no-curl.sh
INPUT=$(cat)
if echo "$INPUT" | grep -q 'curl'; then
echo "curl 명령은 이 프로젝트에서 금지입니다" >&2
exit 2
fi
exit 0
이 훅을 걸고 클로드에게 curl -s https://example.com을 실행해 달라고 했더니, 명령은 돌지 않고 다음처럼 답했습니다. stderr에 쓴 문구가 그대로 전달된 것입니다.
curl 실행이 막혔습니다. 이 프로젝트에 걸린 훅이 curl 명령을 금지하고 있어서 실행할 수 없었습니다. 훅이 돌려준 메시지 그대로입니다: "curl 명령은 이 프로젝트에서 금지입니다"」
차단 사유를 적어 주면 클로드가 왜 막혔는지 알고 다른 방법을 제안합니다. 사유 없이 막으면 같은 명령을 계속 재시도하니, 한 줄이라도 이유를 써 주는 편이 좋습니다.
5. 방법 ② — JSON으로 막기 (사유를 명확히)
종료 코드 0으로 끝내면서 표준 출력에 JSON을 뱉으면 더 세밀하게 제어할 수 있습니다. PreToolUse에서는 hookSpecificOutput.permissionDecision에 "allow" / "deny" / "ask"를 넣습니다.
#!/bin/bash
# .claude/hooks/block-rm.sh
INPUT=$(cat)
if echo "$INPUT" | grep -q 'rm -rf'; then
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"rm -rf 는 훅에서 차단했습니다"}}'
fi
exit 0
아무 출력 없이 exit 0이면 "판단 보류"라 평소대로 허가 절차를 탑니다. 즉 막을 때만 JSON을 뱉으면 됩니다. 실제로 이 훅을 걸고 rm -rf sample.txt를 시켜 보니 파일은 그대로 남아 있었고, 헤드리스 실행 결과(--output-format json)의 permission_denials 항목에 차단된 호출이 기록돼 있었습니다.
같은 hookSpecificOutput 안에서 쓸 수 있는 필드가 몇 개 더 있습니다.
| 필드 | 하는 일 |
|---|---|
permissionDecision |
PreToolUse에서 allow/deny/ask 결정 |
permissionDecisionReason |
그렇게 결정한 이유(클로드에게 보인다) |
updatedInput |
막는 대신 도구 인자를 바꿔치기한다 |
additionalContext |
클로드에게 참고 정보를 덧붙인다 |
최상위에는 continue(false면 클로드를 아예 멈춤), stopReason, systemMessage(사용자에게 경고 표시), suppressOutput도 쓸 수 있습니다.
6. 방법 ③ — 저장한 뒤 자동으로 뭔가 시키기
가장 실용적인 쓰임은 파일을 고친 뒤 포맷터·린터를 자동으로 돌리는 것입니다. PostToolUse에 Write|Edit matcher를 걸면 됩니다.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}/.claude/hooks/after-write.sh\"",
"statusMessage": "포맷 검사 중..."
}
]
}
]
}
}
스크립트에서는 tool_input.file_path로 방금 고친 파일 경로를 꺼내 쓰면 됩니다. 실제로 Write 한 번에 훅이 정확히 한 번 돌아가는 것을 로그로 확인했습니다. statusMessage를 넣으면 훅이 도는 동안 그 문구가 화면에 표시돼서, 갑자기 멈춘 것처럼 보이지 않습니다.
.claude/settings.json의 hooks 항목을 한 번 읽어 보는 습관을 들이는 게 좋습니다.
7. 훅이 안 도는 것 같을 때
- JSON이 깨졌나 —
settings.json에 쉼표 하나가 빠져도 훅 전체가 조용히 무시됩니다. 먼저 파일이 올바른 JSON인지 확인하세요. - matcher 이름이 맞나 — 도구 이름은 대소문자를 구분합니다.
bash가 아니라Bash,edit가 아니라Edit입니다. - 경로가 맞나 — 상대 경로 대신
${CLAUDE_PROJECT_DIR}를 쓰고, 경로에 공백이 있으면 따옴표로 감싸세요. - 윈도우에서 셸이 다른가 — 셸 형태로 적은 명령은 윈도우에서 PowerShell로 넘어갑니다. bash 스크립트를 돌리려면 예제처럼
bash "경로"로 명시하거나 훅 설정에"shell": "bash"를 주세요. - 막으려는데 안 막히나 —
PostToolUse는 도구가 이미 실행된 뒤라 막을 수 없습니다. 차단은PreToolUse에서 해야 합니다.
훅 말고도 클로드 코드의 동작을 손보는 방법이 있습니다. 매번 반복하는 절차를 명령어로 묶고 싶다면 claude skills 사용법이 맞고, 외부 서비스에 손을 뻗게 하려면 Claude MCP 서버 추천 쪽입니다. 훅은 그중에서 "모델이 뭐라고 판단하든 반드시 실행되어야 하는 일"을 담당합니다.
자주 묻는 질문 (FAQ)
Q. 훅을 고치면 클로드 코드를 다시 켜야 하나요?
설정 파일 변경은 세션 도중에도 반영됩니다. 다만 훅은 여러분 권한으로 명령을 실행하는 만큼, 바꾼 뒤에는 의도대로 도는지 한 번 시험해 보세요.
Q. 특정 명령에만 훅을 걸 수 있나요?
훅 설정에 "if": "Bash(rm *)"처럼 조건을 달면 그 형태의 호출에만 훅이 돕니다. 스크립트 안에서 문자열을 검사하는 대신 설정 단계에서 걸러 낼 수 있습니다.
Q. 셸 명령 말고 다른 것도 실행할 수 있나요?
type에 "command" 외에 "http"(이벤트 데이터를 지정한 주소로 전송), "mcp_tool", "prompt", "agent"도 쓸 수 있습니다. 이 글에서는 직접 확인한 command 타입만 다뤘으니, 나머지는 공식 문서를 확인하세요.
Q. 훅을 전부 잠깐 끄려면요?
설정에 "disableAllHooks": true를 넣으면 됩니다.
마무리
정리하면 훅은 이벤트를 고르고, matcher로 대상을 좁히고, 명령을 지정하는 세 단계입니다. 차단은 PreToolUse에서 종료 코드 2 또는 permissionDecision: "deny"로, 사후 처리는 PostToolUse로 합니다. 처음부터 복잡하게 만들 필요 없이 rm -rf 차단 훅 하나부터 걸어 보세요. 열 줄이면 끝나고, 프롬프트로 부탁하던 것을 규칙으로 못박는 감각이 바로 옵니다. 이벤트 종류와 필드가 자주 늘어나는 영역이라 세부 옵션은 공식 문서를 함께 보시길 권합니다.
📚 참고 출처 (2026년 7월 22일 확인 · Claude Code v2.1.204 기준)
· Claude Code 공식 문서 — Hooks reference
· 본문의 실행 결과·입력 JSON은 v2.1.204에서 직접 훅을 걸어 확인한 것입니다.

COMMENTS