• Claude Code์˜ ๋ผ์ดํ”„์‚ฌ์ดํด ํŠน์ • ์‹œ์ ์—์„œ ์ž๋™์œผ๋กœ ์‹คํ–‰๋˜๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ์…ธ ๋ช…๋ น ๋˜๋Š” LLM ํ”„๋กฌํ”„ํŠธ
  • PreToolUse, PostToolUse, Stop ๋“ฑ ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•˜์—ฌ ๊ฒ€์ฆ, ์ฐจ๋‹จ, ์ปจํ…์ŠคํŠธ ์ฃผ์ž… ์ˆ˜ํ–‰
  • 3๊ฐ€์ง€ ํ•ธ๋“ค๋Ÿฌ ํƒ€์ž… ์ง€์›: command (์…ธ), prompt (LLM ๋‹จ์ผํ„ด), agent (๋„๊ตฌ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ subagent)

ํ•ด๋‹น ๊ฐœ๋…์ด ํ•„์š”ํ•œ ์ด์œ 

  • ์œ„ํ—˜ํ•œ ๋ช…๋ น ์ž๋™ ์ฐจ๋‹จ: rm -rf ๊ฐ™์€ ํŒŒ๊ดด์  ๋ช…๋ น์„ ์‚ฌ์ „์— ์ฐจ๋‹จ
  • ์ฝ”๋“œ ํ’ˆ์งˆ ์ž๋™ ๊ฒ€์ฆ: ํŒŒ์ผ ์ˆ˜์ • ํ›„ ์ž๋™์œผ๋กœ ๋ฆฐํŒ…/ํฌ๋งคํŒ… ์‹คํ–‰
  • ์›Œํฌํ”Œ๋กœ์šฐ ์ž๋™ํ™”: ์„ธ์…˜ ์‹œ์ž‘ ์‹œ ํ™˜๊ฒฝ ์„ค์ •, ์ข…๋ฃŒ ์‹œ ์ •๋ฆฌ ์ž‘์—… ๋“ฑ

AS-IS (Hooks ์—†์ด)

sequenceDiagram
    autonumber
    actor User
    participant Claude
    participant System as ํŒŒ์ผ ์‹œ์Šคํ…œ

    User->>Claude: "์ฝ”๋“œ ์ˆ˜์ •ํ•ด์ค˜"
    Claude->>System: Write (ํŒŒ์ผ ์ €์žฅ)
    Note over System: โš ๏ธ ๋ฆฐํŒ… ์•ˆ ๋จ<br/>ํฌ๋งคํŒ… ์•ˆ ๋จ

    Claude->>System: Bash "rm -rf /"
    Note over System: โš ๏ธ ์œ„ํ—˜ํ•œ ๋ช…๋ น<br/>์‚ฌ์ „ ๊ฒ€์ฆ ์—†์Œ

    Note over User: ๋งค๋ฒˆ ์ˆ˜๋™์œผ๋กœ<br/>"๋ฆฐํŠธ ๋Œ๋ ค์ค˜"<br/>"์œ„ํ—˜ํ•œ ๊ฑฐ ํ•˜์ง€๋งˆ" ์š”์ฒญ

TO-BE (Hooks ์‚ฌ์šฉ)

sequenceDiagram
    autonumber
    actor User
    participant Claude
    participant Hook as Hook Handler
    participant System as ํŒŒ์ผ ์‹œ์Šคํ…œ

    User->>Claude: "์ฝ”๋“œ ์ˆ˜์ •ํ•ด์ค˜"
    Claude->>Hook: PreToolUse (Bash "rm -rf")
    Hook-->>Claude: โŒ exit 2 "์ฐจ๋‹จ๋จ"
    Note over Claude: ์œ„ํ—˜ํ•œ ๋ช…๋ น ์ž๋™ ์ฐจ๋‹จ

    Claude->>System: Write (ํŒŒ์ผ ์ €์žฅ)
    System-->>Hook: PostToolUse (Write)
    Hook->>System: ์ž๋™ ๋ฆฐํŒ…/ํฌ๋งคํŒ… ์‹คํ–‰
    Hook-->>Claude: โœ… "๋ฆฐํŠธ ํ†ต๊ณผ"

Hook ์ด๋ฒคํŠธ ์ „์ฒด ๋ชฉ๋ก

์ด๋ฒคํŠธ๋ฐœ์ƒ ์‹œ์ ์ฐจ๋‹จ ๊ฐ€๋Šฅ
SessionStart์„ธ์…˜ ์‹œ์ž‘/์žฌ๊ฐœ ์‹œNo
UserPromptSubmit์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ ์ œ์ถœ ํ›„, Claude ์ฒ˜๋ฆฌ ์ „Yes
PreToolUse๋„๊ตฌ ํ˜ธ์ถœ ์‹คํ–‰ ์ „Yes
PermissionRequestํผ๋ฏธ์…˜ ๋‹ค์ด์–ผ๋กœ๊ทธ ํ‘œ์‹œ ์‹œYes
PostToolUse๋„๊ตฌ ํ˜ธ์ถœ ์„ฑ๊ณต ํ›„No (ํ”ผ๋“œ๋ฐฑ๋งŒ)
PostToolUseFailure๋„๊ตฌ ํ˜ธ์ถœ ์‹คํŒจ ํ›„No (ํ”ผ๋“œ๋ฐฑ๋งŒ)
Notification์•Œ๋ฆผ ๋ฐœ์ƒ ์‹œNo
SubagentStartClaude Code Subagent ์ƒ์„ฑ ์‹œNo
SubagentStopClaude Code Subagent ์™„๋ฃŒ ์‹œYes
StopClaude ์‘๋‹ต ์™„๋ฃŒ ์‹œYes
TaskCompletedํƒœ์Šคํฌ ์™„๋ฃŒ ํ‘œ์‹œ ์‹œYes
PreCompact์ปจํ…์ŠคํŠธ ์••์ถ• ์ „No
SessionEnd์„ธ์…˜ ์ข…๋ฃŒ ์‹œNo

์„ค์ • ๊ตฌ์กฐ (3๋‹จ๊ณ„ ์ค‘์ฒฉ)

{
  "hooks": {
    "PostToolUse": [           // 1. Hook ์ด๋ฒคํŠธ ์„ ํƒ
      {
        "matcher": "Edit|Write",  // 2. Matcher๋กœ ํ•„ํ„ฐ๋ง (regex)
        "hooks": [                // 3. ํ•ธ๋“ค๋Ÿฌ ์ •์˜
          {
            "type": "command",
            "command": "/path/to/lint.sh"
          }
        ]
      }
    ]
  }
}

3๊ฐ€์ง€ ํ•ธ๋“ค๋Ÿฌ ํƒ€์ž…

ํƒ€์ž…type์„ค๋ช…๊ธฐ๋ณธ timeout
Command"command"์…ธ ๋ช…๋ น ์‹คํ–‰. stdin์œผ๋กœ JSON ์ž…๋ ฅ, exit code๋กœ ๊ฒฐ๊ณผ600์ดˆ
Prompt"prompt"LLM์— ํ”„๋กฌํ”„ํŠธ ์ „์†ก, {ok: true/false} ์‘๋‹ต30์ดˆ
Agent"agent"Read/Grep/Glob ๋„๊ตฌ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ subagent ์ƒ์„ฑ60์ดˆ

Exit Code ๊ทœ์น™

Exit Code์˜๋ฏธ๋™์ž‘
0์„ฑ๊ณตstdout์˜ JSON ํŒŒ์‹ฑ, ๋„๊ตฌ ํ˜ธ์ถœ ํ—ˆ์šฉ
2์ฐจ๋‹จstderr๋ฅผ Claude์— ์—๋Ÿฌ๋กœ ์ „๋‹ฌ, ๋„๊ตฌ ํ˜ธ์ถœ ์ฐจ๋‹จ
๊ธฐํƒ€๋น„์ฐจ๋‹จ ์—๋Ÿฌstderr๋ฅผ verbose ๋ชจ๋“œ์— ํ‘œ์‹œ, ์‹คํ–‰ ๊ณ„์†

์„ค์ • ํŒŒ์ผ ์œ„์น˜

์œ„์น˜๊ฒฝ๋กœ์ ์šฉ ๋ฒ”์œ„
Personal~/.claude/settings.json๋‚ด ๋ชจ๋“  ํ”„๋กœ์ ํŠธ
Project.claude/settings.jsonํ•ด๋‹น ํ”„๋กœ์ ํŠธ (๊ณต์œ  ๊ฐ€๋Šฅ)
Local.claude/settings.local.jsonํ•ด๋‹น ํ”„๋กœ์ ํŠธ (gitignore)
Plugin<plugin>/hooks/hooks.jsonํ”Œ๋Ÿฌ๊ทธ์ธ ํ™œ์„ฑํ™”๋œ ๊ณณ
Claude Code Skills / Claude Code Subagentfrontmatter์— ์ •์˜ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ ํ™œ์„ฑ ์‹œ

PreToolUse ์‹ค์ „ ์˜ˆ์‹œ: ์œ„ํ—˜ ๋ช…๋ น ์ฐจ๋‹จ

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
 
if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "ํŒŒ๊ดด์  ๋ช…๋ น ์ฐจ๋‹จ๋จ"
    }
  }'
else
  exit 0
fi
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": ".claude/hooks/block-rm.sh"
      }]
    }]
  }
}

PostToolUse ์‹ค์ „ ์˜ˆ์‹œ: ์ž๋™ ํฌ๋งคํŒ…

#!/bin/bash
# .claude/hooks/auto-format.sh
 
# stdin์—์„œ JSON ์ž…๋ ฅ ์ฝ๊ธฐ
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
 
# ํŒŒ์ผ ํ™•์žฅ์ž์— ๋”ฐ๋ผ ํฌ๋งคํ„ฐ ์‹คํ–‰
case "$FILE_PATH" in
  *.ts|*.tsx|*.js|*.jsx)
    npx prettier --write "$FILE_PATH" 2>/dev/null
    ;;
  *.py)
    black "$FILE_PATH" 2>/dev/null
    ;;
  *.dart)
    dart format "$FILE_PATH" 2>/dev/null
    ;;
esac
 
exit 0
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": ".claude/hooks/block-rm.sh"
      }]
    }],
    "PostToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "command",
        "command": ".claude/hooks/auto-format.sh"
      }]
    }]
  }
}

Stop Hook ๋™์ž‘ ์›๋ฆฌ

  • LLM์ด โ€œ์ž‘์—… ์™„๋ฃŒโ€๋กœ ํŒ๋‹จํ•˜๊ณ  ์ข…๋ฃŒํ•˜๋ ค๋Š” ์ˆœ๊ฐ„ Claude Code ์‹œ์Šคํ…œ์ด ์ž๋™ ํŠธ๋ฆฌ๊ฑฐ
  • ๊ฐœ๋ฐœ์ž๊ฐ€ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์ž๋™ ๊ฐ์‹œ
  • approve โ†’ ์ •์ƒ ์ข…๋ฃŒ, block โ†’ ์ข…๋ฃŒ ๊ฑฐ๋ถ€ + reason์„ Claude์— ์ „๋‹ฌํ•˜์—ฌ ์ž‘์—… ๊ณ„์†
sequenceDiagram
    autonumber
    participant Claude
    participant System as Claude Code Runtime
    participant Hook as Stop Hook Handler
    participant State as State File

    Claude->>System: "์ž‘์—… ์™„๋ฃŒ, ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค"
    System->>Hook: Stop ์ด๋ฒคํŠธ ์ž๋™ ํŠธ๋ฆฌ๊ฑฐ
    Hook->>State: .ai-dev-cycle-state.json ์ฝ๊ธฐ
    State-->>Hook: currentPhase: 2

    alt currentPhase < 5 (๋ฏธ์™„๋ฃŒ)
        Hook-->>System: โŒ block + "Phase 2 ๋ฏธ์™„๋ฃŒ"
        System-->>Claude: "Phase 3-5๋ฅผ ์™„๋ฃŒํ•˜์„ธ์š”"
        Note over Claude: ์ข…๋ฃŒ ๊ฑฐ๋ถ€๋จ<br/>๋‚จ์€ Phase ๊ณ„์† ์ˆ˜ํ–‰
    else currentPhase >= 5 (์™„๋ฃŒ)
        Hook-->>System: โœ… approve
        Note over Claude: ์ •์ƒ ์ข…๋ฃŒ
    end

Decision Output ๊ตฌ์กฐ

ํ•„๋“œํƒ€์ž…ํ•„์ˆ˜์„ค๋ช…
decision"approve" | "block"Yes์ข…๋ฃŒ ํ—ˆ์šฉ ๋˜๋Š” ๊ฑฐ๋ถ€
reasonstringNo๊ฑฐ๋ถ€ ์‚ฌ์œ  (Claude์— ์ „๋‹ฌ)
systemMessagestringNo์ถ”๊ฐ€ ์ปจํ…์ŠคํŠธ (Claude์— ์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€๋กœ ์ „๋‹ฌ)
{
  "decision": "block",
  "reason": "Phase 3 (Result Report) not completed",
  "systemMessage": "currentPhase is 2. Complete Phase 3-5 before stopping."
}

Stop Hook ์‹ค์ „ ์˜ˆ์‹œ: ์›Œํฌํ”Œ๋กœ์šฐ Phase ์™„๋ฃŒ ๊ฒ€์ฆ

#!/bin/bash
# ~/.claude/scripts/ai-dev-cycle-guard.sh
# ai-dev-cycle ์›Œํฌํ”Œ๋กœ์šฐ์˜ Phase ์™„๋ฃŒ๋ฅผ ๊ฒ€์ฆํ•˜๋Š” Stop Hook
 
STATE_FILE=".claude/.ai-dev-cycle-state.json"
 
# state file์ด ์—†์œผ๋ฉด ai-dev-cycle์ด ์•„๋‹˜ โ†’ ํ†ต๊ณผ
if [ ! -f "$STATE_FILE" ]; then
  exit 0
fi
 
CURRENT_PHASE=$(jq -r '.currentPhase // 0' "$STATE_FILE")
 
if [ "$CURRENT_PHASE" -lt 5 ]; then
  jq -n \
    --arg phase "$CURRENT_PHASE" \
    '{
      "decision": "block",
      "reason": ("Phase " + $phase + " ์ง„ํ–‰ ์ค‘. Phase 5(Retrospective)๊นŒ์ง€ ์™„๋ฃŒ ํ›„ ์ข…๋ฃŒํ•˜์„ธ์š”."),
      "systemMessage": ("currentPhase: " + $phase + ". Complete remaining phases before stopping.")
    }'
  exit 0
fi
 
# Phase 5 ์™„๋ฃŒ โ†’ ์ข…๋ฃŒ ํ—ˆ์šฉ
exit 0
{
  "hooks": {
    "Stop": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.claude/scripts/ai-dev-cycle-guard.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

Hook์˜ ํ•œ๊ณ„ โ€” ์Šคํ… ๊ฐ•์ œ์˜ ํ˜„์‹ค์  ๋ฒ”์œ„

Hook์ด ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ vs ์—†๋Š” ๊ฒƒ

๊ตฌ๋ถ„๋ณด์žฅ ๊ฐ€๋Šฅ๋ณด์žฅ ๋ถˆ๊ฐ€
Phase ๋‹จ์œ„Phase 5 ์™„๋ฃŒ ์ „ ์ข…๋ฃŒ ์ฐจ๋‹จPhase ๋‚ด๋ถ€ Step ์ƒ๋žต ๊ฐ์ง€
๋„๊ตฌ ๋‹จ์œ„ํŠน์ • ๋„๊ตฌ ํ˜ธ์ถœ ์ „/ํ›„ ๊ฒ€์ฆStep ์ˆœ์„œ ๊ฐ•์ œ
์ข…๋ฃŒ ์‹œ์ ์กฐ๊ฑด ๋ฏธ์ถฉ์กฑ ์‹œ blockStep์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  Phase๋งŒ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ
flowchart LR
    subgraph "Hook์ด ๊ฐ์ง€ ๊ฐ€๋Šฅ"
        A["Phase 2 โ†’ Phase 5<br/>ํ†ต์งธ๋กœ ์ƒ๋žต"] -->|Stop Hook| B["โŒ block"]
    end

    subgraph "Hook์ด ๊ฐ์ง€ ๋ถˆ๊ฐ€"
        C["Step 1 โ†’ Step 3 ์ƒ๋žต<br/>โ†’ Step 7 ์งํ–‰"] -->|"?"| D["๊ฐ์ง€ ๋ชปํ•จ"]
    end

LangGraph์™€์˜ ๊ตฌ์กฐ์  ์ฐจ์ด

LangGraphClaude Code Hook
์ œ์–ด ์ฃผ์ฒด์ฝ”๋“œ (State Machine)LLM (์ž์œจ ํŒ๋‹จ)
์Šคํ… ๊ฐ•์ œadd_conditional_edges๋กœ ๊ตฌ์กฐ์  ๋ณด์žฅํ”„๋กฌํ”„ํŠธ๋กœ ์ง€์‹œ (์šฐํšŒ ๊ฐ€๋Šฅ)
Evaluator ๋ฃจํ”„์กฐ๊ฑด ๋ถˆ์ถฉ์กฑ ์‹œ ์ด์ „ ๋…ธ๋“œ๋กœ ์ž๋™ ๋˜๋Œ๋ฆผ๋ถˆ๊ฐ€
๊ฒ€์ฆ ์‹œ์ ๋งค ๋…ธ๋“œ ์ „ํ™˜๋งˆ๋‹คStop / PreToolUse ๋“ฑ ์ด๋ฒคํŠธ๋งŒ
์œ ์—ฐ์„ฑ๋‚ฎ์Œ (์‚ฌ์ „ ์ •์˜ ํ•„์ˆ˜)๋†’์Œ (์ž์—ฐ์–ด ๋Œ€์‘)
flowchart TB
    subgraph "LangGraph โ€” ์ฝ”๋“œ๊ฐ€ ์ˆœ์„œ ๊ฐ•์ œ"
        L1[Step 1] -->|"conditional_edge"| L2[Step 2]
        L2 -->|"conditional_edge"| L3[Evaluator]
        L3 -->|FAIL| L2
        L3 -->|PASS| L4[Step 3]
    end

    subgraph "Claude Code โ€” LLM์ด ์ž์œจ ํŒ๋‹จ"
        C1[Step 1] -.->|"ํ”„๋กฌํ”„ํŠธ ์ง€์‹œ"| C2[Step 2]
        C2 -.->|"์ƒ๋žต ๊ฐ€๋Šฅ"| C3[Step 3]
        C3 -.->|"Stop ์‹œ๋„"| C4{Stop Hook}
        C4 -->|block| C3
        C4 -->|approve| C5[์ข…๋ฃŒ]
    end

๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค: 3๊ณ„์ธต ๋ฐฉ์–ด

flowchart TD
    A["1๊ณ„์ธต: ํ”„๋กฌํ”„ํŠธ ๊ฐ•ํ™”<br/>(Critical Rules)"] -->|"๋Œ€๋ถ€๋ถ„ ์ž‘๋™<br/>LLM ์„ ์˜ ์˜์กด"| B["2๊ณ„์ธต: State File ์ถ”์ <br/>(Step ๋‹จ์œ„ ๊ธฐ๋ก)"]
    B -->|"๊ธฐ๋ก ์ž์ฒด๋ฅผ<br/>์ƒ๋žตํ•  ์ˆ˜ ์žˆ์Œ"| C["3๊ณ„์ธต: Stop Hook<br/>(Phase ์™„๋ฃŒ ๊ฒ€์ฆ)"]
    C -->|"Phase ํ†ต์งธ๋กœ<br/>์ƒ๋žต ๋ฐฉ์ง€"| D["์ข…๋ฃŒ ํ—ˆ์šฉ"]

    style A fill:#e8f5e9
    style B fill:#fff3e0
    style C fill:#ffebee
๊ณ„์ธต๋ฐฉ๋ฒ•๊ฐ•์ œ๋ ฅ๋ฐฉ์–ด ๋ฒ”์œ„
1๊ณ„์ธตํ”„๋กฌํ”„ํŠธ ๊ฐ•ํ™” (Critical Rules)๋‚ฎ์ŒStep ๋‹จ์œ„ (LLM์ด ์ค€์ˆ˜ํ•  ๋•Œ)
2๊ณ„์ธตState File (Step ๊ธฐ๋ก)์ค‘๊ฐ„Step ๋‹จ์œ„ (๊ธฐ๋ก ์‹œ)
3๊ณ„์ธตStop Hook (command)๋†’์ŒPhase ๋‹จ์œ„ (์šฐํšŒ ๋ถˆ๊ฐ€)
  • 100% ๋ณด์žฅ์€ ๋ถˆ๊ฐ€๋Šฅ โ€” Claude Code๋Š” LLM์ด ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ์ด๋ฏ€๋กœ ์ฝ”๋“œ ๋ ˆ๋ฒจ ๊ฐ•์ œ๊ฐ€ ์—†์Œ
  • 3๊ณ„์ธต ์กฐํ•ฉ์ด ํ˜„์‹ค์  ์ตœ์„  โ€” Phase ์ƒ๋žต์€ Stop Hook์ด, Step ์ƒ๋žต์€ ํ”„๋กฌํ”„ํŠธ๊ฐ€ ๋ฐฉ์–ด

์ฐธ๊ณ  ๋ฌธ์„œ