← 모든 글

Claude Code Hooks: 95개 Hook 각각이 존재하는 이유

Claude Code용 Hook을 95개 만들었습니다. 모든 Hook은 무언가 먼저 잘못되었기 때문에 존재합니다. git-safety-guardian은 Claude가 main에 force-push를 했기 때문에 존재합니다. recursion-guard는 서브에이전트가 무한히 자식을 생성했기 때문에 존재합니다. blog-quality-gate는 수동태 문장 7개와 누락된 각주가 포함된 글을 게시했기 때문에 존재합니다.1

TL;DR

Claude Code Hook은 AI 보조 개발 과정의 특정 라이프사이클 시점에서 셸 명령을 실행합니다. Hook은 확률적 시스템(LLM) 위에 결정론적 보장(위험한 git 명령 차단, 컨텍스트 주입, 품질 강제)을 제공합니다. 제 인프라 전반에 걸쳐 95개의 Hook을 구축한 결과, 최고의 Hook은 계획이 아니라 인시던트에서 탄생한다는 것을 알게 되었습니다. 이 글에서는 아키텍처, 가장 중요한 Hook들의 탄생 배경, 그리고 9개월간의 Hook 개발에서 배운 패턴을 다룹니다.


아키텍처

Claude Code는 Hook이 동작을 가로채거나, 수정하거나, 차단할 수 있는 라이프사이클 이벤트를 제공합니다:2

세션 이벤트

이벤트 발생 시점 제 Hook
SessionStart 새 세션 시작 시 session-start.sh — 날짜 주입, venv 검증, 재귀 상태 초기화
SessionEnd 세션 종료 시 임시 파일 정리

도구 실행 이벤트

이벤트 발생 시점 제 Hook
PreToolUse 도구 실행 전 git-safety-guardian.sh, recursion-guard.sh, credentials-check.sh
PostToolUse 도구 완료 후 post-deliberation.sh, log-bash.sh

응답 이벤트

이벤트 발생 시점 제 Hook
UserPromptSubmit 사용자가 프롬프트 전송 시 컨텍스트 주입기 (날짜, 브랜치, 모델 정보)
Stop Claude 응답 완료 시 deliberation-pride-check.sh, reviewer-stop-gate.sh

모든 Hook은 stdin으로 JSON을 수신하고 stdout을 통해 통신합니다:

{"decision": "block", "reason": "Force push to main is prohibited"}

또는 종료 코드 0으로 조용히 허용합니다.3


탄생 배경: 가장 중요한 Hook들

Hook 1: git-safety-guardian.sh (PreToolUse:Bash)

인시던트: 초기 Claude Code 세션에서 에이전트에게 “git 히스토리를 정리해 달라”고 요청했습니다. 에이전트는 git push --force origin main을 실행했습니다. 이 force push로 공유 브랜치의 3일치 커밋이 덮어써졌습니다. 로컬 백업에서 복구했지만, 4시간에 걸친 복구 과정을 통해 확률적 판단이 파괴적인 git 작업을 제어해서는 안 된다는 확신을 갖게 되었습니다.

Hook:

#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# Only check git commands
echo "$COMMAND" | grep -qE '\bgit\b' || exit 0

# Block force push to main/master
if echo "$COMMAND" | grep -qiE 'git\s+push\s+.*--force.*\b(main|master)\b'; then
    cat << EOF
{"decision": "block", "reason": "Force push to main/master blocked by safety hook"}
EOF
fi

교훈: 이 Hook은 의도를 이해하려 하지 않습니다. 명령 문자열을 패턴 매칭합니다. 단순하고, 결정론적이며, 교묘한 프롬프팅으로 우회할 수 없습니다. 에이전트는 여전히 기능 브랜치에 force-push할 수 있지만(때로는 정당한 경우), main/master는 영구적으로 보호됩니다.4

누적 차단 횟수: 9개월간 8건의 force-push 시도 차단.

Hook 2: recursion-guard.sh (PreToolUse:Task)

인시던트: 심의 시스템을 구축하던 중, 3개의 탐색 서브에이전트를 생성하는 세션을 실행했습니다. 각 서브에이전트는 생성 제한이 없어 자체적으로 서브에이전트를 생성했습니다. 이 재귀로 인해 API 토큰이 정상 속도의 10배로 소모되었습니다. 가속되는 토큰 소진을 확인한 후 세션을 수동으로 종료했습니다.

Hook:

#!/bin/bash
CONFIG_FILE="${HOME}/.claude/configs/recursion-limits.json"
STATE_FILE="${HOME}/.claude/state/recursion-depth.json"

MAX_DEPTH=2
MAX_CHILDREN=5
DELIB_SPAWN_BUDGET=2
DELIB_MAX_AGENTS=12

# Validate integers safely (((VAR++)) crashes with set -e when VAR=0)
is_positive_int() {
    [[ "$1" =~ ^[0-9]+$ ]] && [[ "$1" -gt 0 ]]
}

핵심 설계 결정: 에이전트는 깊이를 증가시키는 대신 부모로부터 생성 예산을 상속받습니다. 루트 에이전트가 budget=12를 가지면 그 예산을 어떤 트리 형태로든 분배할 수 있습니다. 깊이 기반 제한은 너무 경직적입니다(완전히 안전한 깊지만 좁은 체인을 차단합니다).5

누적 차단 횟수: 23건의 무한 생성 시도 차단.

Hook 3: blog-quality-gate.sh (Stop)

인시던트: 수동태 문장 7개, 본문에서 참조되었지만 참고문헌 섹션에 누락된 각주, 그리고 “was implemented by the team”이 첫 문장인 블로그 글을 게시했습니다. 편집기에서는 완성도 있어 보였지만 사람 리뷰어라면 누구나 잡았을 기본적인 품질 검사에 실패했습니다.

Hook: 수정된 블로그 콘텐츠 파일에 대해 12개 모듈 블로그 린터를 실행합니다. 수동태, 누락된 각주, 빠진 메타 설명, 태그되지 않은 코드 블록, 인용 무결성을 검사합니다. 각 발견 사항은 구체적입니다: “47번째 줄: ‘was implemented by the team’에서 수동태 감지됨. 제안: ‘the team implemented.’”

인간 피드백과의 유사점: 이 Hook은 운영자가 아니라 작업물을 비평합니다. “당신이 글을 잘못 씁니다”가 아니라 “47번째 줄에 수동태가 있습니다”라고 말합니다. 인간의 피드백을 건설적으로 만드는 것과 같은 원칙이 자동화된 피드백도 유용하게 만듭니다.


95개 Hook 뒤의 패턴

설정 기반 아키텍처

제 Hook은 하드코딩된 값에서 설정 기반 동작으로 진화했습니다:

~/.claude/configs/
├── recursion-limits.json     # Depth, spawn budgets, timeouts
├── deliberation-config.json  # Consensus thresholds per task type
├── consensus-profiles.json   # security=85%, docs=50%
├── circuit-breaker.json      # Failure mode configurations
└── file-scope-rules.json     # Path-scoped hook application

임계값을 JSON 설정으로 옮기면서 bash 스크립트를 수정하지 않고도 동작을 조정할 수 있게 되었습니다. 보안 관련 합의를 85%로, 문서화를 50%로 설정해야 할 때 설정 변경은 30초면 충분했습니다. 코드 변경이었다면 수정, 테스트, 재배포가 필요했을 것입니다.6

라이프사이클 계층화 패턴

95개의 Hook은 네 개의 계층으로 구성된 안전망을 형성합니다:

계층 1: 예방 (PreToolUse) — 나쁜 일이 발생하기 전에 차단합니다. git-safety-guardian, credentials-check, recursion-guard.

계층 2: 컨텍스트 (UserPromptSubmit, SessionStart) — 에이전트에게 필요한 정보를 주입합니다. 날짜, 브랜치, 프로젝트 컨텍스트, 메모리 항목, 철학 원칙.

계층 3: 검증 (PostToolUse) — 완료된 작업이 기준을 충족하는지 확인합니다. post-deliberation 합의 검사, 출력 로깅.

계층 4: 품질 (Stop) — 최종 출력을 관문으로 검사합니다. Pride check, quality gate, reviewer stop gate.

각 계층은 독립적입니다. PreToolUse Hook이 조용히 실패하더라도 Stop Hook이 여전히 품질 문제를 포착합니다. 심층 방어를 AI 에이전트 동작에 적용한 것입니다.


설정

Hook은 .claude/settings.json에 위치합니다:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "command": "~/.claude/hooks/git-safety-guardian.sh",
        "timeout": 5000
      }
    ],
    "PreToolUse:Task": [
      {
        "command": "~/.claude/hooks/recursion-guard.sh"
      }
    ]
  }
}

matcher 필드는 어떤 도구가 Hook을 트리거하는지 필터링합니다. PreToolUse:Task는 Task 도구 호출에서만 발동하는 matcher의 축약형입니다. 비동기 Hook(async: true)은 차단 없이 백그라운드에서 실행됩니다.7

범위 계층 구조

  1. 사용자 수준 (~/.claude/settings.json) — 모든 프로젝트에 적용됩니다. 제 95개 Hook이 여기에 있습니다.
  2. 프로젝트 수준 (.claude/settings.json) — 프로젝트별 Hook을 추가합니다.
  3. 스킬/서브에이전트 프론트매터 — 특정 컴포넌트의 라이프사이클로 범위가 제한됩니다.8

다시 한다면 달리 했을 것들

25개가 아니라 3개부터 시작하세요. 첫 달에 25개의 Hook을 만들었는데, 그 중 다수는 에이전트가 이미 가지고 있는 컨텍스트를 추가하는 것이었습니다. 모든 도구 호출마다 25개의 Hook을 로드하는 오버헤드는 체감할 수 있었습니다. 결국 실제 가치를 생산하는 Hook(실제 인시던트를 방지하고, 실제 품질 문제를 포착하는 것)으로 정리했습니다.

처음부터 설정 기반으로. 하드코딩된 임계값을 JSON 설정으로 리팩토링하는 데 2주를 소비했습니다. 처음부터 설정 기반으로 시작했다면 그 리팩토링은 불필요했을 것입니다.

테스트 인프라를 조기에. 처음 20개의 Hook에는 테스트가 없었습니다. 테스트 하네스(48개의 bash 통합 테스트)를 추가했을 때, 엣지 케이스에서 조용히 실패하는 Hook 3개를 발견했습니다. 테스트는 Hook #1과 함께 출시되었어야 했습니다.


핵심 시사점

Hook을 시작하는 개발자를 위해: - 세 가지 Hook으로 시작하세요: git 안전 장치(PreToolUse:Bash), 컨텍스트 주입(UserPromptSubmit), 품질 관문(Stop). 추가 Hook은 정당화할 인시던트가 있을 때만 추가하세요 - 의사결정 타이밍 프레임워크를 활용하세요: Hook 아키텍처는 비가역적이므로(95개의 Hook이 의존), Hook을 작성하기 전에 라이프사이클 모델에 투자하세요

Hook을 표준화하는 팀을 위해: - 사용자 수준에서 Hook을 표준화하여 모든 팀원이 동일한 안전 장치를 적용받도록 하세요 - Hook 지표(차단된 작업, 차단된 인시던트)를 추적하여 유지보수 비용을 정당화하세요 - 매월 Hook 로그를 검토하여 자동화할 가치가 있는 새로운 패턴을 식별하세요


참고 문헌


  1. 저자의 Hook 인프라. 9개월(2025-2026)에 걸쳐 6개 라이프사이클 이벤트에 대한 95개 Hook 개발. 

  2. Anthropic, “Claude Code Documentation,” 2025. Hook 라이프사이클 이벤트. 

  3. Anthropic, “Claude Code Documentation,” 2025. Hook 입출력 JSON 형식. 

  4. 저자의 git-safety-guardian.sh. ~/.claude/state/에서 추적된 8건의 force-push 시도 차단. 

  5. 저자의 recursion-guard.sh. ~/.claude/configs/recursion-limits.json에 문서화된 예산 상속 모델. 

  6. 저자의 설정 기반 아키텍처. 모든 Hook 임계값과 규칙을 인코딩하는 14개의 JSON 설정 파일. 

  7. Anthropic, “Claude Code Documentation,” 2025. Hook 설정 및 비동기 실행. 

  8. Anthropic, “Claude Code Documentation,” 2025. Hook 범위 계층 구조.