
Claude Code를 사용하다 보면 단순히 프롬프트를 잘 작성하는 것만으로는 부족한 상황이 생긴다. 예를 들어 코드 수정 후 자동으로 포맷팅을 실행하고 싶거나, 위험한 Bash 명령어를 사전에 차단하고 싶거나, Claude가 도구를 사용하기 전후에 특정 검증 로직을 실행하고 싶을 수 있다. 이때 사용할 수 있는 기능이 Hook(훅)이다.
Hook은 Claude Code의 실행 흐름 중 특정 시점에 사용자가 정의한 명령을 자동으로 실행하는 기능이다. Git에서 commit 전에 pre-commit hook을 실행하는 것처럼, Claude Code에서도 도구 사용 전후, 프롬프트 제출 시점, 세션 시작과 종료 시점 등에 원하는 로직을 끼워 넣을 수 있다. 이 글에서는 Claude Code에서 사용할 수 있는 Hook의 개념, 동작 구조, 설정 방식, 그리고 실제 구현 예시를 정리한다.
Hook(훅)
Hook은 프로그램의 실행 흐름 중 특정 시점에 끼어들어 실행되는 사용자 정의 로직이다. 이름 그대로 실행 흐름의 특정 지점에 “걸어두는 코드”라고 볼 수 있다. Claude Code에서 Hook은 Claude가 특정 작업을 수행하기 전이나 후에 자동으로 실행된다. 예를 들어 Claude가 Bash 명령어를 실행하기 전에 해당 명령어가 위험한 명령인지 검사하거나, 파일을 수정한 뒤 자동으로 formatter를 실행할 수 있다. 간단히 표현하면 다음과 같다.
Claude Code 실행 흐름
↓
특정 이벤트 발생
↓
Hook 실행
↓
계속 진행 또는 차단
여기서 중요한 점은 Hook이 단순한 권장사항이 아니라 실제 실행 흐름에 개입한다는 것이다. CLAUDE.md에 “위험한 명령어를 실행하지 말 것”이라고 작성하는 것은 지시사항에 가깝다. 반면 Hook은 조건에 맞으면 명령어 실행 자체를 차단할 수 있다.
즉, Hook은 프롬프트 기반 규칙보다 더 결정적인 자동화 장치이다.
왜 Hook이 필요한가
Claude Code는 강력하지만, 결국 사용자의 개발 환경에서 파일을 수정하고 명령어를 실행한다. 따라서 다음과 같은 상황을 제어할 필요가 있다.
- 파일 수정 후 자동 포맷팅을 실행하고 싶을 때
rm -rf,git push --force같은 위험한 명령어를 막고 싶을 때- 특정 파일이나 디렉터리 수정을 제한하고 싶을 때
- Claude가 도구를 사용하기 전에 사용자 정책을 적용하고 싶을 때
- 세션 시작 시 프로젝트 컨텍스트를 자동으로 주입하고 싶을 때
- 작업 종료 시 로그를 남기거나 알림을 보내고 싶을 때
이런 작업을 매번 프롬프트로 요청하는 것은 번거롭고, 누락될 가능성도 있다. Hook을 사용하면 이러한 규칙을 설정 파일에 등록해 자동으로 적용할 수 있다.
Claude Code Hook의 기본 구조
Claude Code Hook은 일반적으로 설정 파일에 정의한다. 프로젝트 단위로 관리하려면 .claude/settings.json에 작성하고, 개인 로컬 환경에서만 사용하려면 .claude/settings.local.json에 작성할 수 있다.
기본 구조는 다음과 같다.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/check-command.py"
}
]
}
]
}
}
이 설정은 Claude가 Bash 도구를 사용하기 직전에 check-command.py를 실행한다는 의미이다.
각 요소의 의미는 다음과 같다.
| 항목 | 의미 |
hooks |
Hook 설정의 최상위 항목 |
PreToolUse |
Hook이 실행될 이벤트 시점 |
matcher |
어떤 도구나 조건에 Hook을 적용할지 지정 |
type |
Hook 실행 방식 |
command |
실제 실행할 명령어 |
즉, Hook 설정은 “언제”, “무엇에 대해”, “어떤 명령을 실행할지”를 정의하는 구조이다.
Matcher 동작 방식
Matcher 값은 문자열 형태이며, 포함된 문자에 따라 평가 방식이 달라진다.
| Matcher 값 | 평가 방식 | 예제 |
| "*", "", 생략 | 모든 경우 매칭 | 모든 이벤트에서 실행 |
| 문자/숫자/_/ | 만 포함 | 정확한 문자열 또는 OR 매칭 |
| 기타 문자 포함 | 정규식 (JavaScript regex) | ^Notebook, mcp__.* |
참고로 이벤트별 matcher 적용 대상이 다르다. 이벤트는 Hook에서 좀 더 자세히 설명해두었다. 이벤트별 matcher 내용은 다음 표와 같다.
| 이벤트 | Matcher | 대상 예시 |
| PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied | 도구 이름 | Bash, `Edit |
| SessionStart | 세션 시작 방식 | startup, resume |
| Setup | CLI 플래그 | init, maintenance |
| SessionEnd | 종료 이유 | logout, clear |
| Notification | 알림 타입 | permission_prompt, idle_prompt |
| SubagentStart / Stop | 에이전트 유형 | Plan, Explore |
| PreCompact / PostCompact | 압축 트리거 | manual, auto |
| ConfigChange | 설정 변경 소스 | user_settings, project_settings |
| StopFailure | 오류 유형 | rate_limit, server_error |
| InstructionsLoaded | 로드 이유 | session_start, include |
| UserPromptExpansion | 명령 이름 | skill 이름 |
| Elicitation / Result | MCP 서버 이름 | 서버 이름 |
| FileChanged | 파일명 | .env, .envrc |
| CwdChanged | matcher 없음 | 항상 실행 |
| UserPromptSubmit, Stop 등 일부 | matcher 없음 | 항상 실행 |
Hook Lifecycle과 이벤트 종류
Claude Code Hook은 이벤트 기반으로 동작한다. 이벤트는 Claude Code의 실행 흐름 중 Hook이 개입할 수 있는 시점을 의미한다. 다음 그림은 Hook의 이벤트 타이밍을 도식화한 내용이다.
전체 이벤트는 다음과 같다. 자주쓰는 이벤트도 함께 밑줄로 표기해두었다.
| 이벤트 | 실행 시점 | 대표 용도 |
| SessionStart | 세션이 시작되거나 재개될 때 | 프로젝트 규칙 주입, 초기 컨텍스트 설정 |
| Setup | --init, --maintenance 실행 시 | CI 초기화, 환경 세팅 |
| UserPromptSubmit | 사용자가 프롬프트 제출 시 | 입력 검증, 컨텍스트 자동 추가 |
| UserPromptExpansion | 명령 확장 전 | 커맨드 확장 차단, 검증 |
| PreToolUse | 도구 실행 직전 | 위험 명령어 차단, 권한 검사 |
| PermissionRequest | 권한 요청 시 | 사용자 승인 로직 커스터마이징 |
| PermissionDenied | 실행 차단 시 | 재시도 유도, 정책 대응 |
| PostToolUse | 도구 실행 성공 후 | 포맷팅, 로그 기록 |
| PostToolUseFailure | 도구 실행 실패 후 | 오류 로그, 자동 복구 시도 |
| PostToolBatch | 병렬 작업 완료 후 | 전체 결과 검증 |
| Notification | 알림 발생 시 | Slack, Discord 알림 |
| SubagentStart | 서브 에이전트 시작 | 작업 추적, 로그 시작 |
| SubagentStop | 서브 에이전트 종료 | 결과 검증 |
| TaskCreated | 작업 생성 시 | 작업 기록, 초기화 |
| TaskCompleted | 작업 완료 시 | 완료 로그, 후처리 |
| Stop | 응답 종료 시 | 테스트 실행, 후처리 |
| StopFailure | API 오류 종료 시 | 오류 대응, 알림 |
| TeammateIdle | 에이전트 idle 직전 | 상태 기록 |
| InstructionsLoaded | CLAUDE.md 로드 시 | 규칙 검증 |
| ConfigChange | 설정 변경 시 | 동적 설정 반영 |
| CwdChanged | 디렉터리 변경 시 | 환경 재설정 |
| FileChanged | 파일 변경 감지 시 | 자동 빌드, reload |
| WorktreeCreate | worktree 생성 시 | Git 환경 설정 |
| WorktreeRemove | worktree 제거 시 | 정리 작업 |
| PreCompact | 컨텍스트 압축 전 | 중요 데이터 유지 |
| PostCompact | 컨텍스트 압축 후 | 상태 재구성 |
| Elicitation | 사용자 입력 요청 시 | 인터랙션 처리 |
| ElicitationResult | 사용자 응답 후 | 입력 검증 |
| SessionEnd | 세션 종료 시 | 로그 저장, 정리 |
가장 많이 사용되는 이벤트는 PreToolUse와 PostToolUse이다.
PreToolUse는 실행 전에 개입하므로 차단 목적에 적합하다. 반면 PostToolUse는 실행 후에 개입하므로 포맷팅, 테스트, 로그 기록 같은 후처리에 적합하다.
PreToolUse 예시: 위험한 Bash 명령어 차단
가장 대표적인 Hook 예시는 위험한 Bash 명령어를 차단하는 것이다. 예를 들어 Claude가 다음 명령어를 실행하려고 한다고 가정한다.
rm -rf /
이런 명령어는 실수로 실행되면 큰 문제가 발생할 수 있다. 따라서 Bash 실행 전에 Hook을 걸어 위험한 패턴을 검사하는 것이 좋다.
먼저 .claude/settings.json에 다음과 같이 Hook을 등록한다.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/block-dangerous-command.py"
}
]
}
]
}
}
그리고 .claude/hooks/block-dangerous-command.py 파일을 작성한다.
#!/usr/bin/env python3
import json
import re
import sys
payload = json.load(sys.stdin)
command = payload.get("tool_input", {}).get("command", "")
dangerous_patterns = [
r"rm\s+-rf\s+/",
r"git\s+push\s+--force",
r"DROP\s+TABLE",
r"dd\s+if=",
]
for pattern in dangerous_patterns:
if re.search(pattern, command, re.IGNORECASE):
print(f"위험한 명령어가 차단됨: {command}", file=sys.stderr)
sys.exit(2)
sys.exit(0)
여기서 핵심은 종료 코드이다.
0: 정상 통과2: 실행 차단
즉, Hook 스크립트가 sys.exit(2)를 반환하면 Claude Code는 해당 도구 실행을 차단한다.
이 구조를 통해 개발자는 단순히 “위험한 명령어를 쓰지 말라”고 요청하는 수준을 넘어, 실제 실행 자체를 막을 수 있다.
PostToolUse 예시: 파일 수정 후 자동 포맷팅
PostToolUse는 도구 사용 이후에 실행된다. 따라서 파일 수정 후 formatter를 실행하는 용도로 적합하다.
예를 들어 Claude가 파일을 수정한 뒤 Python 파일에 대해 black을 자동 실행하고 싶다면 다음과 같이 설정할 수 있다.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/format-python.py"
}
]
}
]
}
}
스크립트는 다음과 같이 작성할 수 있다.
#!/usr/bin/env python3
import json
import subprocess
import sys
from pathlib import Path
payload = json.load(sys.stdin)
file_path = payload.get("tool_input", {}).get("file_path")
if not file_path:
sys.exit(0)
path = Path(file_path)
if path.suffix == ".py":
subprocess.run(["black", str(path)], check=False)
sys.exit(0)
이렇게 하면 Claude가 Python 파일을 수정한 뒤 자동으로 black이 실행된다.
Claude가 파일 수정
↓
PostToolUse Hook 실행
↓
black formatter 실행
↓
정리된 코드 유지
이 방식은 코드 스타일을 유지하는 데 유용하다. 특히 팀 프로젝트에서 공통 formatter를 강제하고 싶을 때 효과적이다.
UserPromptSubmit 예시: 프롬프트 제출 시 컨텍스트 추가
UserPromptSubmit은 사용자가 프롬프트를 제출한 직후 실행된다. 이 이벤트는 Claude가 본격적으로 응답하기 전에 컨텍스트를 추가하거나, 입력 내용을 검사할 때 사용할 수 있다.
예를 들어 매번 현재 Git 브랜치와 변경 파일 목록을 Claude에게 알려주고 싶다면 다음과 같이 구성할 수 있다.
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/inject-git-context.sh"
}
]
}
]
}
}
.claude/hooks/inject-git-context.sh는 다음과 같이 작성할 수 있다.
#!/usr/bin/env bash
echo "현재 Git 컨텍스트:"
echo "브랜치: $(git branch --show-current)"
echo "변경 파일:"
git status --short
이 Hook을 사용하면 매번 사용자가 직접 “현재 브랜치 확인해줘”, “변경 파일 보고 작업해줘”라고 말하지 않아도 된다. Claude는 프롬프트 처리 전에 현재 프로젝트 상태를 함께 참고할 수 있다.
SessionStart 예시: 프로젝트 규칙 자동 주입
SessionStart는 Claude Code 세션이 시작될 때 실행된다. 프로젝트마다 반드시 지켜야 하는 규칙이나 작업 방식을 자동으로 주입하는 데 적합하다.
예를 들어 다음과 같은 규칙을 항상 Claude에게 전달하고 싶다고 가정한다.
- 테스트 없이 리팩토링하지 않는다
- main 브랜치에서는 직접 수정하지 않는다
- Controller에서 Repository를 직접 호출하지 않는다
설정은 다음과 같이 작성할 수 있다.
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "cat .claude/project-rules.md"
}
]
}
]
}
}
.claude/project-rules.md에는 프로젝트 규칙을 작성한다.
# Project Rules
- 테스트 없이 리팩토링하지 않는다.
- main 브랜치에서는 직접 수정하지 않는다.
- Controller에서 Repository를 직접 호출하지 않는다.
- 변경 전후로 관련 테스트를 확인한다.
이렇게 하면 세션 시작 시 프로젝트 규칙이 자동으로 Claude에게 전달된다.
Hook의 입력과 출력
Claude Code는 Hook 스크립트에 JSON 형태의 입력을 전달한다. Hook 스크립트는 이 JSON을 읽고, 조건에 따라 통과하거나 차단할 수 있다. 구조를 단순화하면 다음과 같다.
Claude Code
↓ JSON 입력(stdin)
Hook Script
↓ 종료 코드 / 출력
Claude Code
예를 들어 PreToolUse에서는 어떤 도구를 사용하려는지, 해당 도구에 어떤 입력이 전달되는지 확인할 수 있다.
import json
import sys
payload = json.load(sys.stdin)
print(payload)
Hook을 처음 작성할 때는 위처럼 입력 payload를 출력해 구조를 확인하는 것이 좋다. 이벤트마다 전달되는 JSON 구조가 다를 수 있기 때문이다.
Hook 타입
Claude Code Hook은 여러 실행 방식을 지원한다. 가장 기본적인 방식은 command 타입이다.
| 타입 | 설명 | 사용 예 |
command |
로컬 셸 명령 실행 | Python/Bash 스크립트 실행 |
http |
HTTP 엔드포인트 호출 | 사내 API, Slack Webhook 호출 |
prompt |
LLM 프롬프트 기반 평가 | 정책 검사, 응답 검증 |
agent |
별도 agent 실행 | 복잡한 검토나 분석 작업 |
일반적인 자동화는 대부분 command 타입으로 충분하다. 로컬 파일 검사, 명령어 차단, formatter 실행, 로그 기록은 모두 command 타입으로 구현할 수 있다.
외부 시스템과 연동해야 한다면 http 타입을 사용할 수 있다. 예를 들어 Claude가 작업을 끝냈을 때 Slack으로 알림을 보내는 식이다.
Hook과 CLAUDE.md의 차이
Claude Code를 사용할 때 프로젝트 규칙을 작성하는 대표적인 방법은 CLAUDE.md이다. 하지만 CLAUDE.md와 Hook은 역할이 다르다.
| 구분 | CLAUDE.md | Hook |
| 목적 | Claude에게 지침 제공 | 실행 흐름에 직접 개입 |
| 성격 | 설명, 규칙, 컨텍스트 | 자동화, 검증, 차단 |
| 강제력 | 상대적으로 약함 | 조건에 따라 실행 차단 가능 |
| 예시 | “테스트를 먼저 확인하라” | 테스트 미통과 시 작업 차단 |
즉, CLAUDE.md는 Claude에게 알려주는 문서이고, Hook은 실제로 실행되는 코드이다.
따라서 중요한 정책은 Hook으로 처리하는 것이 더 안전하다. 예를 들어 “운영 설정 파일을 수정하지 말라”는 지침은 CLAUDE.md에 적는 것보다 PreToolUse Hook으로 차단하는 것이 더 확실하다.
실전 예시: 운영 설정 파일 수정 차단
운영 설정 파일은 실수로 수정되면 큰 장애로 이어질 수 있다. 따라서 Claude가 특정 파일을 수정하려고 할 때 차단하는 Hook을 만들 수 있다. 설정은 다음과 같다.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/protect-prod-files.py"
}
]
}
]
}
}
스크립트는 다음과 같다.
#!/usr/bin/env python3
import json
import sys
from pathlib import Path
payload = json.load(sys.stdin)
file_path = payload.get("tool_input", {}).get("file_path", "")
protected_files = [
"application-prod.yml",
"application-prod.properties",
".env.production",
]
path = Path(file_path)
if path.name in protected_files:
print(f"보호된 운영 설정 파일 수정 차단: {file_path}", file=sys.stderr)
sys.exit(2)
sys.exit(0)
이제 Claude가 application-prod.yml 같은 파일을 수정하려고 하면 Hook이 먼저 실행되고, 조건에 맞으면 작업이 차단된다.
실전 예시: 작업 종료 후 테스트 실행
작업이 끝난 뒤 자동으로 테스트를 실행하고 싶다면 Stop 이벤트를 사용할 수 있다.
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "npm test"
}
]
}
]
}
}
또는 Java 프로젝트라면 다음과 같이 사용할 수 있다.
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "./gradlew test"
}
]
}
]
}
}
다만 테스트 시간이 긴 프로젝트에서는 모든 응답 종료 시마다 테스트를 실행하는 것이 부담이 될 수 있다. 이 경우 특정 파일 수정 후에만 실행하거나, 별도 스크립트에서 조건을 분기하는 방식이 좋다.
Hook을 사용할 때 주의할 점
Hook은 강력하지만 잘못 작성하면 오히려 개발 흐름을 방해할 수 있다.
첫째, 너무 많은 Hook을 한 번에 적용하지 않는 것이 좋다. 처음에는 위험 명령어 차단, 파일 보호, 자동 포맷팅처럼 효과가 명확한 것부터 적용하는 것이 좋다.
둘째, Hook 스크립트는 빠르게 실행되어야 한다. 모든 도구 사용 전후에 실행되는 Hook이 느리면 Claude Code 사용 경험이 크게 나빠진다.
셋째, 차단 조건은 명확해야 한다. 너무 넓은 패턴으로 차단하면 정상적인 명령까지 막을 수 있다.
넷째, 팀에서 공유할 설정과 개인 설정을 구분해야 한다. 팀 공통 규칙은 .claude/settings.json에 두고, 개인 알림이나 로컬 환경 의존 설정은 .claude/settings.local.json에 두는 것이 좋다.
Hook을 적용하기 좋은 경우
Hook은 다음과 같은 작업에 특히 잘 맞는다.
- Bash 명령어 보안 검사
- 운영 파일 수정 방지
- 코드 포맷팅 자동 실행
- 테스트 자동 실행
- 세션 시작 시 프로젝트 규칙 주입
- 작업 종료 후 로그 기록
- Slack, Discord 등 외부 알림 전송
- Git 상태 자동 첨부
반대로 복잡한 판단이 필요하거나 매번 상황에 따라 달라지는 작업은 Hook보다 프롬프트나 별도 agent에 맡기는 것이 나을 수 있다.
Claude Code Hook은 Claude의 실행 흐름에 사용자가 직접 개입할 수 있게 해주는 자동화 기능이다. 단순히 “이렇게 해줘”라고 지시하는 것이 아니라, 특정 시점에 실제 스크립트를 실행하고 필요하면 작업을 차단할 수 있다는 점에서 강력하다.
특히 PreToolUse는 위험한 명령어 차단이나 파일 보호에 적합하고, PostToolUse는 포맷팅이나 후처리에 적합하다. UserPromptSubmit과 SessionStart는 프로젝트 컨텍스트를 자동으로 주입할 때 유용하다.
정리하면 Hook은 Claude Code를 더 안전하고 일관된 개발 도구로 만들기 위한 장치이다. 프롬프트는 Claude에게 방향을 알려주고, Hook은 반드시 지켜야 하는 규칙을 코드로 강제한다.
따라서 Claude Code를 실무 프로젝트에서 사용한다면 Hook은 단순 편의 기능이 아니라 개발 워크플로우를 제어하는 핵심 기능이라고 볼 수 있다.
관련 포스팅
2026.05.05 - [AI/AI Tools] - Claude Code Skills vs Codex Skills: 구조와 차이 완전 정리
참고 자료
https://javaexpert.tistory.com/1528
https://code.claude.com/docs/ko/hooks-guide
https://code.claude.com/docs/ko/hooks
https://news.hada.io/topic?id=21772