커리큘럼 흐름
- CLAUDE.md Mastery — 저장소 메모리와 규칙
- Effective Prompting — 작업 framing과 제약 설정
- MCP Power Tools — 도구와 라이브 컨텍스트 연결
- Multi-Agent Workflows — 위임과 병렬 실행
- Hooks Automation — 로컬 워크플로우 enforcement ← 현재 문서
- GitHub Actions Workflows — 반복 작업을 팀 자동화로 옮기기
이 문서에서 참고한 공식 문서
왜 Hooks가 중요한가
Claude Code는 단순한 편집기를 넘어서 팀의 워크플로우를 자동으로 강제할 때 훨씬 유용해집니다.
hooks를 쓰면 도구 사용 전후와 라이프사이클 이벤트에 맞춰 셸 명령을 실행할 수 있습니다. 그래서 다음이 가능해집니다.
- 편집 후 자동 포맷팅
- 민감한 작업 전 lint/test 실행
- 긴 작업 완료 알림
- 민감한 경로 수정 차단
- compact 전 컨텍스트 보존
Hook 라이프사이클
Claude Code가 도구를 실행할 때마다 이런 흐름이 일어납니다.
사용자 요청
↓
PreToolUse hook 실행 ← 여기서 차단하거나 사전 검사 가능
↓
도구 실행 (Edit, Write, Bash 등)
↓
PostToolUse hook 실행 ← 여기서 후처리, 포맷팅, 알림 가능
↓
Claude 응답
PreToolUse에서 exit 코드를 1로 반환하면 도구 실행 자체가 막힙니다. 이걸 이용해서 파일 보호 같은 가드레일을 만들 수 있어요.
Hook 설정 방법
hooks는 .claude/settings.json에 등록합니다. 프로젝트 루트에 파일이 없다면 만들어 주세요.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"command": ".claude/hooks/protect-files.sh"
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"command": ".claude/hooks/auto-format.sh"
}
],
"PreCompact": [
{
"matcher": ".*",
"command": ".claude/hooks/save-context.sh"
}
],
"Notification": [
{
"matcher": ".*",
"command": ".claude/hooks/notify-done.sh"
}
]
}
}- matcher: 정규식으로 어떤 도구에 적용할지 지정합니다.
"Edit|Write"라고 쓰면 Edit과 Write 도구 모두에 적용됩니다. - command: 실행할 셸 명령 또는 스크립트 경로입니다.
- hook 스크립트들은
chmod +x로 실행 권한을 줘야 합니다.
실전 훅 예시
1. 편집 후 자동 포맷팅 (PostToolUse)
파일을 수정할 때마다 자동으로 Prettier를 실행합니다. TypeScript와 TSX 파일에만 적용하는 예시예요.
#!/bin/bash
# .claude/hooks/auto-format.sh
FILE=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.file_path // empty')
# 파일 경로가 없으면 조용히 종료
[[ -z "$FILE" ]] && exit 0
# TypeScript 파일에만 Prettier 적용
if [[ "$FILE" == *.ts || "$FILE" == *.tsx ]]; then
npx prettier --write "$FILE" 2>/dev/null
echo "Formatted: $FILE"
fiCLAUDE_TOOL_INPUT 환경변수에 도구가 받은 입력이 JSON으로 들어있습니다. jq로 파싱해서 필요한 정보를 꺼내 쓰면 됩니다.
2. 민감한 파일 보호 (PreToolUse)
.env, 마이그레이션 파일, 생성된 코드는 Claude가 실수로 수정하지 못하도록 막을 수 있습니다.
#!/bin/bash
# .claude/hooks/protect-files.sh
FILE=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.file_path // empty')
# 보호할 경로 패턴 목록
PROTECTED=(
".env"
".env.local"
".env.production"
"prisma/migrations"
"generated/"
"__generated__"
)
for pattern in "${PROTECTED[@]}"; do
if [[ "$FILE" == *"$pattern"* ]]; then
echo "BLOCKED: '$FILE' is a protected file. Edit manually if this is intentional." >&2
exit 1
fi
done
exit 0exit 1로 종료하면 Claude Code가 도구 실행을 멈추고 오류 메시지를 보여줍니다. exit 0이면 정상 진행입니다.
3. 컨텍스트 보존 (PreCompact)
대화가 길어지면 Claude Code가 오래된 내용을 압축(compact)합니다. 그 전에 중요한 상태를 파일로 저장해 두면 맥락을 이어갈 수 있어요.
#!/bin/bash
# .claude/hooks/save-context.sh
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
SAVE_DIR=".claude/snapshots"
mkdir -p "$SAVE_DIR"
# 현재 작업 상태를 스냅샷으로 저장
cat > "$SAVE_DIR/context-$TIMESTAMP.md" << EOF
# Context Snapshot — $TIMESTAMP
## Git 상태
$(git status --short 2>/dev/null || echo "git 없음")
## 최근 변경 파일
$(git diff --name-only HEAD 2>/dev/null | head -20 || echo "없음")
## 메모
컴팩트 전 자동 저장됨.
EOF
echo "Context saved to $SAVE_DIR/context-$TIMESTAMP.md"4. 작업 완료 알림 (Notification)
긴 작업이 끝날 때 터미널을 계속 보고 있지 않아도 됩니다. macOS라면 osascript로 데스크톱 알림을 보낼 수 있어요.
#!/bin/bash
# .claude/hooks/notify-done.sh
# macOS 데스크톱 알림
if command -v osascript &>/dev/null; then
osascript -e 'display notification "Claude Code가 작업을 완료했어요" with title "Claude Code" sound name "Glass"'
fi
# Linux (notify-send) 대응
if command -v notify-send &>/dev/null; then
notify-send "Claude Code" "작업이 완료됐어요"
fi모델링 방식
hooks는 이벤트 기반 가드레일이라고 생각하면 됩니다.
가치가 큰 활용법
.env, migration, generated code 보호- 수정 후 lint/tsc 실행
- compact 전 상태 저장
- 긴 작업 종료 시 알림 전송
디버깅 팁
hooks가 예상대로 동작하지 않을 때 시도해볼 것들입니다.
1. 스크립트를 단독으로 테스트하기
# 실제 환경변수를 흉내 내서 직접 실행
export CLAUDE_TOOL_INPUT='{"file_path": "src/app.ts"}'
bash .claude/hooks/auto-format.sh
echo "Exit code: $?"2. stderr로 로그 남기기
hook 스크립트 안에서 >&2로 출력하면 Claude Code의 오류 스트림에 나타납니다.
echo "DEBUG: FILE=$FILE" >&23. exit 코드 확인
PreToolUse hook에서 0이 아닌 값으로 종료하면 도구가 차단됩니다. 의도하지 않게 차단되고 있다면 exit 코드를 먼저 확인하세요.
4. jq 설치 확인
which jq || echo "jq가 없습니다 — brew install jq 또는 apt install jq"보안
Anthropic은 hooks가 로컬 시스템에서 임의 셸 명령을 실행한다고 분명히 말합니다.
중요한 습관:
- 입력 검증
- 절대 경로 사용
- 셸 변수 quoting
- path traversal 차단
- secrets/민감 파일 회피
- 안전한 환경에서 먼저 테스트
전체 Hook 이벤트 레퍼런스
Claude Code가 지원하는 모든 hook 이벤트 타입을 정리했습니다.
| 이벤트 | 실행 시점 | 활용 예시 |
|---|---|---|
PreToolUse |
도구 실행 전 | 위험한 작업 차단, 입력 검증 |
PostToolUse |
도구 실행 후 | 자동 포맷팅, 린트, 알림 전송 |
PreCompact |
컨텍스트 압축 전 | 중요한 상태 저장, 스냅샷 생성 |
PostCompact |
컨텍스트 압축 후 | 컨텍스트 복원, 핵심 규칙 재주입 |
Notification |
Claude가 알림을 보낼 때 | 데스크톱 알림, Slack 메시지, 사운드 효과 |
Stop |
Claude가 응답을 끝냈을 때 | 세션 정리, 최종 검증, 자동 저장 |
알아둘 점:
- 각 이벤트마다 받는 환경변수가 다릅니다
PreToolUse만 실행을 차단할 수 있습니다 (exit 1). 나머지는 모두 advisory(참고용)입니다- matcher 패턴은 정규식을 지원합니다:
"Edit|Write","Bash",".*"(전체 도구)
환경변수 완전 레퍼런스
hook 스크립트에서 사용할 수 있는 환경변수 목록입니다.
| 변수 | 사용 가능한 이벤트 | 내용 |
|---|---|---|
CLAUDE_TOOL_NAME |
모든 hook | 사용 중인 도구 이름 (Edit, Write, Bash 등) |
CLAUDE_TOOL_INPUT |
모든 hook | 도구 파라미터가 담긴 JSON (file_path, command 등) |
CLAUDE_TOOL_OUTPUT |
PostToolUse만 | 도구 실행 결과가 담긴 JSON |
CLAUDE_SESSION_ID |
모든 hook | 현재 세션 식별자 |
CLAUDE_PROJECT_DIR |
모든 hook | 프로젝트 루트 디렉토리 경로 |
CLAUDE_MODEL |
모든 hook | 현재 모델 (claude-sonnet-4-6 등) |
- JSON 입력은
jq로 파싱합니다:echo "$CLAUDE_TOOL_INPUT" | jq -r '.file_path' - 변수가 없을 수 있으니 항상 기본값 처리를 해주세요
PostCompact 훅 활용
컨텍스트 압축은 오래된 대화 내용을 지워서 토큰을 확보합니다. 문제는 중요한 규칙이나 맥락도 함께 사라질 수 있다는 점이에요.
PostCompact hook은 압축이 끝난 뒤 실행되어, 중요한 컨텍스트를 다시 주입할 수 있습니다.
#!/bin/bash
# .claude/hooks/post-compact.sh
# 압축 후 핵심 컨텍스트를 다시 주입
cat << 'CONTEXT'
[Post-Compact Context Restoration]
- Current task: Review authentication module
- Important constraint: Do NOT modify database schema
- Active branch: feature/auth-refactor
- Files modified so far: src/auth/login.ts, src/auth/session.ts
CONTEXTsettings에 등록하는 방법:
{
"hooks": {
"PostCompact": [
{
"matcher": ".*",
"command": ".claude/hooks/post-compact.sh"
}
]
}
}- PostCompact hook의 출력은 대화 컨텍스트로 다시 주입됩니다
- 짧게 유지하세요 — 길면 압축의 의미가 없어집니다
실전: 로컬 CI 파이프라인 훅 체인
여러 hook을 조합해서 완전한 로컬 CI 파이프라인을 구축할 수 있습니다. 파일을 수정할 때마다 타입 체크, 린트, 테스트가 자동으로 실행됩니다.
#!/bin/bash
# .claude/hooks/local-ci.sh — Edit|Write에 대한 PostToolUse hook
FILE=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.file_path // empty')
[[ -z "$FILE" ]] && exit 0
ERRORS=""
# Step 1: TypeScript 타입 체크 (.ts/.tsx 파일만)
if [[ "$FILE" == *.ts || "$FILE" == *.tsx ]]; then
if ! npx tsc --noEmit --pretty 2>/tmp/tsc-output.txt; then
ERRORS+="TypeScript errors found:\n$(cat /tmp/tsc-output.txt)\n\n"
fi
fi
# Step 2: ESLint 검사
if [[ "$FILE" == *.ts || "$FILE" == *.tsx || "$FILE" == *.js ]]; then
if ! npx eslint "$FILE" --quiet 2>/tmp/eslint-output.txt; then
ERRORS+="ESLint issues:\n$(cat /tmp/eslint-output.txt)\n\n"
fi
fi
# Step 3: 관련 테스트 실행
TEST_FILE="${FILE%.ts}.test.ts"
if [[ -f "$TEST_FILE" ]]; then
if ! npx jest "$TEST_FILE" --silent 2>/tmp/test-output.txt; then
ERRORS+="Test failures:\n$(cat /tmp/test-output.txt)\n\n"
fi
fi
# 결과 리포트
if [[ -n "$ERRORS" ]]; then
echo "⚠ Local CI found issues:" >&2
echo -e "$ERRORS" >&2
else
echo "✓ Local CI passed: types, lint, tests" >&2
fi
exit 0 # 차단하지 않음 — 리포트만 제공핵심 포인트:
- 파일 수정 후 자동으로 실행됩니다
- stderr로 리포트하여 Claude가 피드백으로 볼 수 있습니다
- exit 0이라서 advisory — Claude가 경고를 보지만 차단되지는 않습니다
- 강제하고 싶으면 PreToolUse에서 exit 1로 바꾸세요
전체 파이프라인을 위한 settings.json 설정:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"command": ".claude/hooks/protect-files.sh"
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"command": ".claude/hooks/auto-format.sh"
},
{
"matcher": "Edit|Write",
"command": ".claude/hooks/local-ci.sh"
}
],
"PreCompact": [
{
"matcher": ".*",
"command": ".claude/hooks/save-context.sh"
}
],
"PostCompact": [
{
"matcher": ".*",
"command": ".claude/hooks/post-compact.sh"
}
],
"Notification": [
{
"matcher": ".*",
"command": ".claude/hooks/notify-done.sh"
}
]
}
}Hooks vs CLAUDE.md
| 도구 | 적합한 역할 |
|---|---|
CLAUDE.md |
규칙, 아키텍처, 코딩 정책 |
| hooks | 자동 enforcement |
| 프롬프트 | 오늘의 작업 |
Claude Code vs Codex
Codex는 sandbox/approval 경계에 더 기대고, Claude Code hooks는 로컬 이벤트 기반 자동화에 더 기대는 편입니다.