← 모든 글

Claude Code Hooks 튜토리얼: 처음부터 만드는 5가지 프로덕션 Hooks

From the guide: Claude Code Comprehensive Guide

Claude Code는 약 95%의 경우 올바른 작업을 수행합니다. 나머지 5%에는 main에 강제 푸시하거나, 포맷터를 건너뛰거나, lint에 실패하는 코드를 커밋하는 경우가 포함됩니다. Hooks는 Claude의 워크플로우에서 17개의 라이프사이클 지점에 결정론적 게이트를 추가하여 이 5%를 제거합니다. 프롬프트 표현이나 모델 동작에 관계없이 매번 예외 없이 실행됩니다.

요약: Hooks는 Claude Code 라이프사이클 이벤트에 의해 트리거되는 셸 명령입니다. PreToolUse hooks는 작업을 검사하고 차단합니다(종료 코드 2 = 차단, 종료 코드 0 = 허용). PostToolUse hooks는 사후에 검증하고 포맷합니다. .claude/settings.json에서 matcher 정규식과 중첩된 hooks 배열로 설정합니다. 이 튜토리얼에서는 자동 포맷터, 보안 게이트, 테스트 러너, 알림, 커밋 전 품질 검사의 5가지 프로덕션 hooks를 구축합니다.

핵심 요약

  • 개인 개발자: 자동 포맷터(Hook 1)와 보안 게이트(Hook 2)부터 시작하세요. 이 두 hooks는 유지보수 부담 없이 가장 흔한 Claude Code 실수를 방지합니다.
  • 팀 리더: hooks를 저장소의 .claude/settings.json에 커밋하세요. 모든 팀원이 동일한 안전 게이트와 품질 검사를 자동으로 적용받습니다.
  • 보안 엔지니어: 종료 코드 2는 작업을 차단합니다. 종료 코드 1은 경고만 기록합니다. 모든 PreToolUse 보안 hook은 반드시 exit 2를 사용해야 하며, 그렇지 않으면 아무런 강제력이 없습니다.

Hooks란 무엇인가?

Hooks는 Claude Code 세션 중 특정 라이프사이클 이벤트에서 실행되는 셸 명령입니다. 모델이 해석하는 프롬프트가 아니라, Claude의 작업에 의해 트리거되는 일반 스크립트로서 LLM 외부에서 실행됩니다.

네 가지 주요 카테고리가 가장 일반적인 사용 사례를 포괄합니다(Claude Code는 총 17개의 이벤트 유형을 지원합니다):

  • 세션 이벤트: SessionStartStop은 세션이 시작되거나 종료될 때 실행됩니다. 설정, 정리, 알림에 사용합니다.
  • 도구 이벤트: PreToolUsePostToolUse는 Claude가 도구를 사용하기 전과 후에 실행됩니다(파일 쓰기, bash 명령 실행, 코드 검색 등). 특정 작업을 검사하고 차단할 수 있어 가장 강력한 hooks입니다.
  • 알림 이벤트: Notification은 Claude가 알림을 생성할 때 실행됩니다. Slack, 데스크톱 알림, 로깅 시스템으로 알림을 라우팅하는 데 유용합니다.
  • 서브에이전트 이벤트: SubagentStop은 서브에이전트(Agent 도구를 통해 생성된)가 완료될 때 실행됩니다. Hooks는 서브에이전트 작업에도 실행되므로 안전 게이트가 재귀적으로 적용됩니다.

종료 코드의 의미가 중요합니다. 종료 코드 0은 성공(진행)을 의미합니다. 종료 코드 2는 작업 차단을 의미합니다. 종료 코드 1은 비차단 hook 오류로 작업은 계속 진행됩니다. 모든 보안에 중요한 hook은 실제로 게이트를 강제하기 위해 반드시 exit 2를 사용해야 합니다.


Hook 설정 기본 사항

Hooks는 설정 파일에 위치합니다:

  • 프로젝트 수준: 저장소 루트의 .claude/settings.json (팀과 공유)
  • 사용자 수준: ~/.claude/settings.json (개인 hooks, 전역 적용)

JSON 구조:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/your/script.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/another-script.sh"
          }
        ]
      }
    ]
  }
}

각 항목에는 matcher(도구 이름과 매칭되는 정규식: Bash, Write, Edit, Read, Glob, Grep, Agent 등)와 hook 정의의 hooks 배열이 있습니다. 각 hook은 type(셸 명령의 경우 "command")과 실행할 command를 지정합니다. Write|Edit matcher는 두 도구 유형 모두와 매칭됩니다.

Claude Code 세션 내에서 /hooks 슬래시 명령으로 hooks를 대화형으로 관리할 수도 있습니다.

Hook이 실행될 때 Claude Code는 환경 변수($FILE_PATH는 파일 작업용)와 stdin(도구 이름, 매개변수, 세션 메타데이터가 포함된 JSON 객체)을 통해 컨텍스트를 전달합니다. 스크립트는 이를 읽어서 결정을 내립니다.


5가지 실용적인 Hooks

아래의 각 hook은 Claude Code를 주요 개발 도구로 사용하면서 겪은 실제 문제를 해결합니다. 모든 예제는 올바른 중첩 hook 스키마를 사용합니다.

1. 파일 편집 시 자동 포맷

Claude는 기능적으로 올바른 코드를 작성하지만 때때로 프로젝트의 포맷팅 규칙을 어깁니다. Claude에게 다시 포맷하라고 요청하는 대신, 모든 파일 쓰기 후에 자동으로 포맷터를 실행합니다.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'if [[ \"$FILE_PATH\" == *.py ]]; then black --quiet \"$FILE_PATH\" 2>/dev/null; elif [[ \"$FILE_PATH\" == *.js ]] || [[ \"$FILE_PATH\" == *.ts ]]; then npx prettier --write \"$FILE_PATH\" 2>/dev/null; fi'"
          }
        ]
      }
    ]
  }
}

Claude Code는 $FILE_PATH를 수정된 파일로 설정합니다. Hook은 확장자를 확인하고 적절한 포맷터를 실행합니다. Python 파일은 black을, JavaScript와 TypeScript 파일은 prettier를 실행합니다. 2>/dev/null은 불필요한 출력을 억제하여 실제 오류만 표시합니다.

대규모 프로젝트의 경우, 가독성을 위해 인라인 명령을 독립 스크립트로 분리하세요.

2. 위험한 명령에 대한 보안 게이트

Bash 도구에 대한 PreToolUse hooks는 Claude가 실행하려는 명령을 검사하고, 위험한 패턴과 일치하면 차단합니다. 이 hook은 Claude가 리팩토링 세션 중에 main에 강제 푸시한 후 작성했습니다.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r \".tool_input.command\"); if echo \"$CMD\" | grep -qE \"rm\\s+-rf\\s+/|git\\s+push\\s+(-f|--force)\\s+(origin\\s+)?main|git\\s+reset\\s+--hard|DROP\\s+TABLE|:(){ :|:& };:\"; then echo \"BLOCKED: Dangerous command detected: $CMD\" >&2; exit 2; fi'"
          }
        ]
      }
    ]
  }
}

이 hook이 종료 코드 2로 종료되면 Claude Code는 대기 중인 명령을 취소합니다. 오류 메시지는 터미널과 Claude의 컨텍스트 모두에 출력되어, 모델이 작업이 실패한 이유를 이해하고 더 안전한 대안을 제안합니다.

차단 패턴: - rm -rf / (루트에서 재귀적 삭제) - git push --force maingit push -f main (main 브랜치에 강제 푸시) - git reset --hard (커밋되지 않은 작업 삭제) - DROP TABLE (실수로 인한 데이터베이스 파괴) - 포크 폭탄

환경에 맞게 이 목록을 커스터마이즈하세요. 프로덕션 데이터베이스에는 파괴적인 SQL 패턴이 필요합니다. CLI 기반 배포에는 배포 명령 가드가 필요합니다.

3. 변경 후 테스트 러너

Claude가 Python 파일을 편집하면 관련 테스트를 자동으로 실행합니다. 이렇게 하면 세 개의 파일을 편집한 후에 발견하는 대신 회귀를 즉시 포착할 수 있습니다.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'if [[ \"$FILE_PATH\" == *.py ]] && [[ \"$FILE_PATH\" != *test_* ]]; then TEST_FILE=\"tests/test_$(basename \"$FILE_PATH\")\"; if [[ -f \"$TEST_FILE\" ]]; then python -m pytest \"$TEST_FILE\" -x --tb=short 2>&1 | tail -20; fi; fi'"
          }
        ]
      }
    ]
  }
}

Hook은 편집된 파일이 Python 소스 파일인지(테스트 파일 자체가 아닌지) 확인하고, test_ 접두사 명명 규칙을 사용하여 대응하는 테스트 파일을 찾아 존재하면 실행합니다. -x 플래그는 첫 번째 실패에서 중단하고, tail -20은 출력을 간결하게 유지합니다.

참고: 이 hook은 test_ 접두사 명명 규칙을 사용하는 플랫한 tests/ 디렉토리를 가정합니다. 중첩된 테스트 디렉토리나 다른 명명 규칙에 맞게 경로 구성을 조정하세요.

이 hook은 Claude가 여러 파일을 수정하는 리팩토링 세션에서 특히 유용합니다. 즉각적인 피드백 루프가 테스트를 마지막에만 실행할 때 발생하는 “하나를 고치면 세 개가 깨지는” 연쇄 반응을 방지합니다.

4. 세션 종료 시 알림

장시간 실행되는 Claude Code 세션은 몇 분이 걸릴 수 있습니다. 터미널을 계속 지켜보는 대신, 세션이 완료되면 알림을 받으세요.

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code session ended\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

이 macOS 예제는 osascript를 사용하여 네이티브 알림을 트리거합니다. Linux의 경우 osascript 줄을 notify-send "Claude Code" "Session ended"로 교체하세요. Slack 알림의 경우 웹훅을 사용합니다:

curl -s -X POST "$SLACK_WEBHOOK_URL" \
  -H 'Content-type: application/json' \
  -d '{"text": "Claude Code session ended"}'

저는 & <task>(Claude Code의 백그라운드 모드)로 시작한 백그라운드 작업에 Slack 변형을 사용합니다. 대화형 세션에는 데스크톱 알림을 사용합니다.

5. 커밋 전 품질 검사

Claude가 git commit을 실행하기 전에 코드가 린팅을 통과하는지 검증합니다. 이는 포맷팅만으로는 놓치는 문제를 포착합니다: 사용하지 않는 import, 정의되지 않은 변수, 타입 오류 등.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r \".tool_input.command\"); if echo \"$CMD\" | grep -qE \"^git\\s+commit\"; then if ! LINT_OUTPUT=$(ruff check . --select E,F,W 2>&1); then echo \"LINT FAILED -- fix before committing:\" >&2; echo \"$LINT_OUTPUT\" >&2; exit 2; fi; fi'"
          }
        ]
      }
    ]
  }
}

이 hook은 Bash 명령이 git commit으로 시작할 때만 활성화됩니다. 오류, pyflakes, 경고 규칙으로 ruff(빠른 Python 린터)를 실행합니다. 문제가 있으면 커밋이 차단(exit 2)되고 Claude가 lint 출력을 확인하여 일반적으로 문제를 수정하고 재시도합니다.

여러 품질 검사를 중첩할 수 있습니다: 타입 검사를 위한 mypy, 보안 스캔을 위한 bandit, 또는 프로젝트의 커스텀 검증 스크립트. Bash 명령에 대한 PreToolUse hooks는 모든 셸 작업 전에 프로그래밍 가능한 게이트를 제공합니다.


Hook 디버깅 팁

Hooks는 예상보다 자주 조용히 실패합니다. 디버깅에 사용하는 다섯 가지 기법을 소개합니다:

  1. 먼저 스크립트를 독립적으로 테스트하세요. 샘플 JSON을 스크립트에 수동으로 파이프합니다: echo '{"tool_input":{"command":"git commit -m test"}}' | bash your-hook.sh. Claude Code 외부에서 실패하면 내부에서도 실패합니다.
  2. 디버그 출력에 stderr를 사용하세요. Hook이 stderr에 쓰는 모든 내용은 Claude의 컨텍스트에 표시됩니다. 개발 중에 echo "DEBUG: matched $CMD" >&2를 작성하고, hook이 안정되면 제거하세요.
  3. jq 실패를 주의하세요. JSON 경로가 잘못되면 jq는 조용히 null을 반환하고 조건문이 매칭되지 않습니다. 실제 도구 입력에 대해 jq 표현식을 테스트하세요.
  4. 종료 코드를 확인하세요. 종료 코드 2는 작업을 차단합니다. 종료 코드 1은 경고만 합니다. 실수로 exit 1을 사용하는 PreToolUse hook은 작동하는 것처럼 보이면서 아무런 강제력도 제공하지 않습니다. 기본적으로 허용(exit 0)으로 시작하고 특정 차단 패턴에만 exit 2를 사용하세요.
  5. Hooks를 빠르게 유지하세요. Hooks는 동기적으로 실행됩니다. 5초가 걸리는 hook은 매칭되는 모든 도구 사용에 5초를 추가합니다. 모든 hooks를 2초 이내, 이상적으로는 500밀리초 이내로 유지합니다.

다음 단계

이 다섯 가지 hooks는 포맷팅, 보안, 테스팅, 알림, 품질 게이트의 기본을 다룹니다. 이러한 패턴에 익숙해지면 컨텍스트 주입(세션 시작 시 프로젝트별 지침 추가), 재귀 가드(무한 서브에이전트 루프 방지), 워크플로우 오케스트레이션(다단계 프로세스 체이닝)을 위한 hooks를 구축할 수 있습니다.

Hook 아키텍처, 17개 전체 라이프사이클 이벤트, 고급 패턴에 대해서는 전체 가이드의 hooks 섹션을 참조하세요: Claude Code 가이드: Hooks는 어떻게 작동하나요?

또한 95개 프로덕션 hooks의 탄생 배경에 대해 Claude Code Hooks: 95개 Hooks 각각이 존재하는 이유에서 다루었으며, 각각을 만들게 된 사건들을 설명합니다.


FAQ

Hooks가 Claude Code의 명령 실행을 차단할 수 있나요?

네. PreToolUse hooks는 종료 코드 2로 모든 도구 작업을 차단합니다. Claude Code는 대기 중인 작업을 취소하고 hook의 stderr 출력을 모델에 표시합니다. 종료 코드 1은 비차단 hook 오류로 작업은 계속 진행됩니다. 이 구분은 매우 중요합니다: 모든 보안 hook은 반드시 exit 1이 아닌 exit 2를 사용해야 합니다. Claude는 거부 이유를 확인하고 더 안전한 대안을 제안합니다.

Hook 설정 파일은 어디에 두나요?

Hook 설정은 프로젝트 수준 hooks의 경우 .claude/settings.json(저장소에 커밋, 팀과 공유)에, 사용자 수준 hooks의 경우 ~/.claude/settings.json(개인용, 모든 프로젝트에 적용)에 둡니다. 두 곳 모두 존재하면 프로젝트 수준 hooks가 우선합니다. 작업 디렉토리 문제를 피하기 위해 스크립트 파일에 절대 경로를 사용하는 것을 권장합니다.

Hooks가 서브에이전트에서도 작동하나요?

네. Hooks는 서브에이전트 작업에도 실행됩니다. Claude가 Agent 도구를 통해 서브에이전트를 생성하면, 서브에이전트가 사용하는 모든 도구에 대해 PreToolUse와 PostToolUse hooks가 실행됩니다. 이 동작이 없으면 서브에이전트가 안전 게이트를 우회할 수 있습니다. SubagentStop 이벤트를 사용하면 서브에이전트가 작업을 완료할 때 정리 또는 검증을 실행할 수 있습니다.

관련 게시물

How to Set Up Claude Code CLI: 5-Minute Quickstart

Install Claude Code, configure your first project, and run your first agentic coding session in under 5 minutes. Covers …

13 분 소요

Codex CLI vs Claude Code in 2026: Architecture Deep Dive

Kernel-level sandboxing vs application-layer hooks, AGENTS.md vs CLAUDE.md, cloud tasks vs subagents. A technical compar…

13 분 소요

Claude Code Hooks: Why Each of My 95 Hooks Exists

I built 95 hooks for Claude Code. Each one exists because something went wrong. Here are the origin stories and the arch…

7 분 소요