Claude Code로 돌아가기
Claude Code중급13분 소요

Hooks 자동화 — Claude Code가 워크플로우를 직접 지키게 만들기

Claude Code hooks를 사용해 린팅, 파일 보호, 알림, 워크플로우 가드레일을 자동화하는 방법

hooks자동화워크플로우보안

공식 참고 자료: Hooks · Settings · Overview

커리큘럼 흐름

  1. CLAUDE.md Mastery — 저장소 메모리와 규칙
  2. Effective Prompting — 작업 framing과 제약 설정
  3. MCP Power Tools — 도구와 라이브 컨텍스트 연결
  4. Multi-Agent Workflows — 위임과 병렬 실행
  5. Hooks Automation — 로컬 워크플로우 enforcement ← 현재 문서
  6. GitHub Actions Workflows — 반복 작업을 팀 자동화로 옮기기

이 문서에서 참고한 공식 문서

  • hook 라이프사이클과 설정 모델Hooks
  • hook 스크립트의 보안 고려사항Hooks
  • 설정 표면과 로컬 구성Settings

왜 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"
fi

CLAUDE_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 0

exit 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" >&2

3. 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
CONTEXT

settings에 등록하는 방법:

{
  "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는 로컬 이벤트 기반 자동화에 더 기대는 편입니다.

연결된 가이드