GitHub ↗
CHAPTER 05 OF 10
🔗

Hooks: Event-Driven Automation

Hooks — 이벤트 기반 자동화의 완전 가이드

훅은 Claude Code의 모든 이벤트에 자동화를 연결하는 메커니즘이다 — 파일 저장 시 자동 포맷, 위험 명령 차단, 테스트 자동 실행까지.

Hooks: Event-Driven Automation cheatsheet
🍌 NANO BANANA CHEATSHEET · CH 05

Overview

개관

Hooks는 Claude Code의 가장 강력하고 가장 과소 평가된 기능이다. 파일을 편집할 때마다 자동으로 포맷터를 실행하거나, 위험한 명령을 실행 전에 차단하거나, 세션이 끝날 때 테스트를 자동 실행하거나, AI의 판단이 의심스러울 때 LLM 기반 검증을 추가하거나 — 이 모든 것이 훅으로 가능하다.

훅을 이해하면 Claude Code가 단순한 AI 어시스턴트를 넘어 팀의 자동화 인프라가 된다. 이 챕터에서는 20개 이상의 이벤트 타입, 5가지 핸들러 타입, 그리고 실제로 팀에서 즉시 사용할 수 있는 레시피를 다룬다.

🎯 Learning Goals
  • 훅의 6가지 이벤트 카테고리를 이해한다
  • 5가지 핸들러 타입(command, http, mcp_tool, prompt, agent)의 차이를 설명한다
  • exit code 0, 2, 기타의 동작을 정확히 이해한다
  • PostToolUse에서 자동 포맷 훅을 작성할 수 있다
  • PreToolUse에서 위험 명령을 차단하는 훅을 설계한다

Sections

본문

훅 이벤트 6가지 카테고리

훅 이벤트는 6가지 카테고리로 나뉜다:

1. 세션 생명주기 (비블로킹)

  • SessionStart — 세션 시작 시
  • SessionEnd — 세션 종료 시
  • Setup--init-only로 한 번만 실행

2. 턴 이벤트

  • UserPromptSubmit — 사용자 프롬프트 제출 시 (블로킹, exit 2로 차단 가능)
  • Stop — Claude가 턴을 마칠 때 (exit 2로 계속 작업 강제)
  • StopFailure — 비블로킹 관찰용

3. 도구 실행 (에이전트 루프 핵심)

  • PreToolUse — 모든 도구 호출 전 (블로킹, exit 2로 차단)
  • PostToolUse — 도구 성공 후 (응답 수정 가능)
  • PermissionRequest — 권한 다이얼로그 전 (자동 승인/거부)
  • PostToolBatch — 병렬 도구 배치 완료 후 (블로킹)

4. 에이전트/팀 이벤트

  • SubagentStart / SubagentStop
  • TeammateIdle — 팀원을 계속 작업하게 유지 가능

5. 파일/설정 이벤트

  • FileChanged — 디스크의 파일 변경 감지 (비동기)
  • InstructionsLoaded — CLAUDE.md 로드 시
  • ConfigChange — settings.json 변경 시 (블로킹, 거부 가능)

6. 태스크/컨텍스트 이벤트

  • TaskCreated / TaskCompleted
  • PreCompact / PostCompact — 컨텍스트 압축 전후

5가지 핸들러 타입

각 훅 이벤트에 어떤 종류의 핸들러를 붙일지 선택할 수 있다:

1. command — 가장 일반적. 쉘 명령을 실행한다.

{ "type": "command", "command": "prettier --write ${CLAUDE_TOOL_INPUT_FILE_PATH}" }

2. http — HTTP POST로 외부 서비스에 이벤트를 전송한다.

{ "type": "http", "url": "http://localhost:8080/validate" }

3. mcp_tool — 연결된 MCP 서버의 도구를 호출한다.

{ "type": "mcp_tool", "server": "my_server", "tool": "validate_code" }

4. prompt — 단일 턴 LLM 평가. 빠른 모델로 조건을 검사한다.

{ "type": "prompt", "prompt": "이 쉘 명령이 안전한가? $ARGUMENTS" }

5. agent — Read/Grep/Glob 도구를 가진 서브에이전트를 스폰한다 (실험적).

{ "type": "agent", "prompt": "이 작업이 허용된 범위 내인지 확인하라: $ARGUMENTS" }

prompt와 agent 핸들러는 PreToolUse, PostToolUse, PermissionRequest에서만 사용 가능하다.

Exit Code 시맨틱 — 가장 중요한 이해

훅의 동작은 종료 코드에 의해 결정된다:

종료 코드 이벤트 유형 동작
0 모든 이벤트 성공. stdout의 JSON이 처리됨
2 PreToolUse 도구 호출 차단. stderr가 Claude에 표시됨
2 Stop Claude가 계속 작업하도록 강제
2 UserPromptSubmit 프롬프트 차단
기타 모든 이벤트 비블로킹 오류. stderr 첫 줄만 표시

exit code 2의 이중적 의미가 중요하다. PreToolUse에서는 '이 도구 호출을 막아라'이고, Stop에서는 '아직 작업이 끝나지 않았다 계속해라'이다. 같은 코드가 컨텍스트에 따라 반대 효과를 낸다.

JSON 출력으로 Claude에 피드백을 줄 수 있다:

{
  "systemMessage": "사용자에게 표시할 메시지",
  "continue": false,
  "stopReason": "차단 이유",
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Claude의 컨텍스트에 주입될 텍스트"
  }
}

실전 훅 레시피 — 즉시 사용 가능한 패턴

레시피 1: 파일 저장 시 자동 포맷 (PostToolUse)

{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{
        "type": "command",
        "command": "jq -r '.tool_input.file_path // empty' | { read -r f; [ -n \"f\" ] && prettier --write \"f\"; } 2>/dev/null || true"
      }]
    }]
  }
}

레시피 2: rm -rf 차단 (PreToolUse)

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "if": "Bash(rm -rf *)",
      "hooks": [{
        "type": "command",
        "command": "echo '{\"continue\": false, \"stopReason\": \"rm -rf는 허용되지 않습니다\"}'"
      }]
    }]
  }
}

레시피 3: 세션 종료 시 테스트 실행 (Stop)

{
  "hooks": {
    "Stop": [{
      "matcher": "*",
      "hooks": [{
        "type": "command",
        "command": "npm test --silent && echo '{\"systemMessage\": \"테스트 통과 완료\"}' || echo '{\"continue\": false, \"stopReason\": \"테스트 실패: 수정 후 완료 선언하세요\"}'"
      }]
    }]
  }
}

레시피 4: .env 파일 읽기 차단 (PreToolUse)

{
  "permissions": {
    "deny": ["Read(.env)", "Read(.env.local)", "Read(.env.production)"]
  }
}
💡 Analogy · 비유
공장의 품질 관리 체크포인트

현대 자동차 공장에는 생산 라인의 각 단계마다 품질 검사 포인트가 있다. 어떤 포인트는 문제를 발견하면 라인 전체를 멈춘다(블로킹). 어떤 포인트는 문제를 기록만 하고 라인을 계속 진행시킨다(비블로킹). 어떤 포인트는 발견된 문제를 즉석에서 자동 수정한다.

훅은 Claude Code 에이전트 루프의 품질 관리 체크포인트다. 도구 호출 전(PreToolUse)과 후(PostToolUse)에 검사 로직이 실행된다. 문제 발견 시 exit code 2로 라인을 멈추거나, 자동 수정(포맷터 실행)을 하거나, 그냥 기록만 할 수 있다.

가장 좋은 공장은 가능한 많은 품질 검사를 자동화한다 — 인간 검사자는 자동화로 잡기 어려운 판단의 영역에만 개입한다. 마찬가지로 좋은 훅 설계는 반복 가능한 검사를 자동화하고, 인간 승인은 실제로 판단이 필요한 경우에만 요구한다.

프로덕션에서 실제 사용되는 종합 hooks 설정이다. 포맷, 보안, 테스트 자동화가 하나의 settings.json에 담겨있다.

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path // empty' | { read -r f; [ -n \"$f\" ] && npx prettier --write \"$f\" --ignore-unknown; } 2>/dev/null || true",
            "async": true
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "if": "Bash(git push --force *)",
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"continue\": false, \"stopReason\": \"force push는 허용되지 않습니다. PR을 통해 머지하세요.\"}'"
          }
        ]
      },
      {
        "matcher": "Read",
        "if": "Read(.env*)",
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"continue\": false, \"stopReason\": \".env 파일은 읽을 수 없습니다.\"}'"
          }
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "npm run typecheck --silent 2>&1 | grep -c 'error' | { read n; [ \"$n\" -eq 0 ] && echo '{\"systemMessage\": \"TypeCheck: 통과\"}' || echo '{\"continue\": false, \"stopReason\": \"TypeScript 에러가 있습니다. 수정 후 완료하세요.\"}'; }",
            "async": false
          }
        ]
      }
    ]
  }
}

세 가지 훅이 각자 다른 역할을 한다. PostToolUse의 prettier 훅은 async: true로 파일 저장 후 백그라운드에서 포맷을 실행한다 (메인 흐름을 막지 않음). PreToolUse의 두 훅은 위험 작업을 차단한다 — if 필드로 특정 패턴에만 반응해 불필요한 훅 실행을 줄인다. Stop 훅은 TypeScript 에러가 있으면 완료를 선언하지 못하게 막는다.

🏭 현업에서의 평가
훅을 사용하는 팀은 그렇지 않은 팀보다 코드 품질 관련 PR 리뷰 코멘트가 현저히 적다. 포맷, 타입 체크, 보안 체크 같은 반복적인 품질 게이트를 자동화했기 때문이다.

✅ 시니어가 보는 것

  • 훅이 실제 팀의 pain point를 해결하는지
  • 블로킹 훅과 비블로킹 훅을 적절히 구분하는지
  • exit code 시맨틱을 정확히 이해하고 있는지

⚠️ 레드 플래그

  • 훅이 너무 많아서 Claude의 모든 작업이 지연되는 경우
  • 비블로킹이어야 하는 훅이 블로킹으로 설정된 경우
  • 훅 명령이 실패 시 Claude에 아무 피드백 없이 조용히 실패하는 경우

🎤 예상 인터뷰 질문

  1. Stop 훅에서 exit code 2의 의미가 PreToolUse와 어떻게 다른지 설명해주세요.
  2. 팀에서 가장 많은 가치를 제공한 훅 설정이 무엇인가요?
  3. LLM 기반 prompt 핸들러를 언제 command 핸들러 대신 사용하시나요?
숙달 vs 익숙함: 단순히 아는 수준: 훅을 설정할 수 있다. 마스터 수준: 팀의 품질 관리 전략을 훅 시스템으로 구현하고, exit code와 JSON 응답으로 Claude에 정확한 피드백을 줄 수 있으며, async/sync 실행을 의도적으로 선택한다.

Key Takeaways

핵심 정리

훅은 에이전트 루프의 체크포인트

모든 도구 호출 전후에 자동화를 삽입할 수 있다. 반복 품질 검사를 인간이 하지 않아도 되게 만든다.

exit code 2는 이중 의미

PreToolUse에서는 차단, Stop에서는 계속 작업 강제. 같은 코드가 컨텍스트에 따라 반대 효과다.

if 필드로 과도한 실행 방지

모든 Bash 명령이 아니라 rm -rf* 패턴에만 반응하도록 필터링하라. 불필요한 훅 실행은 성능을 저하시킨다.

async: true로 흐름 보존

포맷터같은 사후 정리 작업은 async로 실행해 Claude의 메인 흐름을 막지 않는다.

JSON 응답으로 Claude에 피드백

단순히 차단하는 것보다 왜 차단되었는지 stopReason으로 알려주면 Claude가 더 적절히 대응한다.