• 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
SubagentStartSubagents 생성 μ‹œNo
SubagentStopSubagents μ™„λ£Œ μ‹œ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 / Subagentsfrontmatter에 μ •μ˜ν•΄λ‹Ή μ»΄ν¬λ„ŒνŠΈ ν™œμ„± μ‹œ

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"
      }]
    }]
  }
}

μ°Έκ³  λ¬Έμ„œ