- 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 |
SubagentStart | Claude Code Subagent ์์ฑ ์ | No |
SubagentStop | Claude Code Subagent ์๋ฃ ์ | Yes |
Stop | Claude ์๋ต ์๋ฃ ์ | 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 Subagent | frontmatter์ ์ ์ | ํด๋น ์ปดํฌ๋ํธ ํ์ฑ ์ |
#!/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 | ์ข
๋ฃ ํ์ฉ ๋๋ ๊ฑฐ๋ถ |
reason | string | No | ๊ฑฐ๋ถ ์ฌ์ (Claude์ ์ ๋ฌ) |
systemMessage | string | No | ์ถ๊ฐ ์ปจํ
์คํธ (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 ์์ ๊ฐ์ |
| ์ข
๋ฃ ์์ | ์กฐ๊ฑด ๋ฏธ์ถฉ์กฑ ์ block | Step์ ๊ฑด๋๋ฐ๊ณ 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์์ ๊ตฌ์กฐ์ ์ฐจ์ด
| LangGraph | Claude 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 ์๋ต์ ํ๋กฌํํธ๊ฐ ๋ฐฉ์ด
์ฐธ๊ณ ๋ฌธ์