SpyBara
Go Premium

hooks-guide.md 2026-07-20 23:01 UTC to 2026-07-21 23:00 UTC

50 added, 50 removed.

2026
Thu 23 15:58 Wed 22 23:59 Tue 21 23:00 Mon 20 23:01 Sat 18 16:02 Fri 17 22:57 Thu 16 22:59 Wed 15 22:00 Tue 14 23:01 Mon 13 23:57 Sat 11 19:03 Fri 10 17:00 Thu 9 23:58 Wed 8 16:02 Tue 7 16:02 Mon 6 23:57 Sat 4 03:01 Fri 3 23:00 Thu 2 23:59 Wed 1 21:01

hooks๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์—… ์ž๋™ํ™”

Claude Code๊ฐ€ ํŒŒ์ผ์„ ํŽธ์ง‘ํ•˜๊ฑฐ๋‚˜ ์ž‘์—…์„ ์™„๋ฃŒํ•˜๊ฑฐ๋‚˜ ์ž…๋ ฅ์ด ํ•„์š”ํ•  ๋•Œ ์ž๋™์œผ๋กœ ์…ธ ๋ช…๋ น์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ ํ˜•์‹ ์ง€์ •, ์•Œ๋ฆผ ์ „์†ก, ๋ช…๋ น ๊ฒ€์ฆ ๋ฐ ํ”„๋กœ์ ํŠธ ๊ทœ์น™ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

Hooks๋Š” Claude Code์˜ ๋ผ์ดํ”„์‚ฌ์ดํด์˜ ํŠน์ • ์ง€์ ์—์„œ ์‹คํ–‰๋˜๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ์…ธ ๋ช…๋ น์ž…๋‹ˆ๋‹ค. ์ด๋“ค์€ Claude Code์˜ ๋™์ž‘์— ๋Œ€ํ•œ ๊ฒฐ์ •๋ก ์  ์ œ์–ด๋ฅผ ์ œ๊ณตํ•˜์—ฌ LLM์ด ์‹คํ–‰ํ•˜๋„๋ก ์„ ํƒํ•˜๋Š” ๊ฒƒ์— ์˜์กดํ•˜๊ธฐ๋ณด๋‹ค๋Š” ํŠน์ • ์ž‘์—…์ด ํ•ญ์ƒ ๋ฐœ์ƒํ•˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. Hooks๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ๊ทœ์น™์„ ์ ์šฉํ•˜๊ณ , ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์„ ์ž๋™ํ™”ํ•˜๋ฉฐ, Claude Code๋ฅผ ๊ธฐ์กด ๋„๊ตฌ์™€ ํ†ตํ•ฉํ•ฉ๋‹ˆ๋‹ค.

ํŒ๋‹จ์ด ํ•„์š”ํ•œ ๊ฒฐ์ •์˜ ๊ฒฝ์šฐ ๊ฒฐ์ •๋ก ์  ๊ทœ์น™์ด ์•„๋‹Œ ๊ฒฝ์šฐ, ํ”„๋กฌํ”„ํŠธ ๊ธฐ๋ฐ˜ hooks ๋˜๋Š” ์—์ด์ „ํŠธ ๊ธฐ๋ฐ˜ hooks๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋“ค์€ Claude ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์กฐ๊ฑด์„ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

Claude Code๋ฅผ ํ™•์žฅํ•˜๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์€ skills๋ฅผ ์ฐธ์กฐํ•˜์—ฌ Claude์— ์ถ”๊ฐ€ ์ง€์นจ๊ณผ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ๋ช…๋ น์„ ์ œ๊ณตํ•˜๊ณ , subagents๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฉ๋ฆฌ๋œ ์ปจํ…์ŠคํŠธ์—์„œ ์ž‘์—…์„ ์‹คํ–‰ํ•˜๋ฉฐ, plugins๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ์ „์ฒด์—์„œ ๊ณต์œ ํ•  ํ™•์žฅ์„ ํŒจํ‚ค์ง•ํ•ฉ๋‹ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ hook ์„ค์ •

Hook์„ ๋งŒ๋“ค๋ ค๋ฉด ์„ค์ • ํŒŒ์ผ์— hooks ๋ธ”๋ก์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ด ์—ฐ์Šต์€ ๋ฐ์Šคํฌํ†ฑ ์•Œ๋ฆผ hook์„ ๋งŒ๋“ค๋ฏ€๋กœ Claude๊ฐ€ ํ„ฐ๋ฏธ๋„์„ ๋ณด๋Š” ๋Œ€์‹  ์ž…๋ ฅ์„ ๊ธฐ๋‹ค๋ฆด ๋•Œ๋งˆ๋‹ค ์•Œ๋ฆผ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.

1

์„ค์ •์— hook ์ถ”๊ฐ€

~/.claude/settings.json์„ ์—ด๊ณ  Notification hook์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์ œ๋Š” macOS์šฉ osascript๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Linux ๋ฐ Windows ๋ช…๋ น์€ Claude๊ฐ€ ์ž…๋ ฅ์ด ํ•„์š”ํ•  ๋•Œ ์•Œ๋ฆผ ๋ฐ›๊ธฐ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}

์„ค์ • ํŒŒ์ผ์— ์ด๋ฏธ hooks ํ‚ค๊ฐ€ ์žˆ์œผ๋ฉด ์ „์ฒด ๊ฐ์ฒด๋ฅผ ๋ฐ”๊พธ๋Š” ๋Œ€์‹  Notification์„ ๊ธฐ์กด ์ด๋ฒคํŠธ ํ‚ค์˜ ํ˜•์ œ๋กœ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ์ด๋ฒคํŠธ ์ด๋ฆ„์€ ๋‹จ์ผ hooks ๊ฐ์ฒด ๋‚ด์˜ ํ‚ค์ž…๋‹ˆ๋‹ค:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }]
}
],
"Notification": [
{
"matcher": "",
"hooks": [{ "type": "command", "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" }]
}
]
}
}

CLI์—์„œ ์›ํ•˜๋Š” ๊ฒƒ์„ ์„ค๋ช…ํ•˜์—ฌ Claude์—๊ฒŒ hook์„ ์ž‘์„ฑํ•˜๋„๋ก ์š”์ฒญํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

2

๊ตฌ์„ฑ ํ™•์ธ

/hooks๋ฅผ ์ž…๋ ฅํ•˜์—ฌ hooks ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์—ฝ๋‹ˆ๋‹ค. ๊ตฌ์„ฑ๋œ hooks๊ฐ€ ์žˆ๋Š” ๊ฐ ์ด๋ฒคํŠธ ์˜†์— ๊ฐœ์ˆ˜๊ฐ€ ์žˆ๋Š” ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  hook ์ด๋ฒคํŠธ ๋ชฉ๋ก์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. Notification์„ ์„ ํƒํ•˜์—ฌ ์ƒˆ hook์ด ๋ชฉ๋ก์— ๋‚˜ํƒ€๋‚˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. Hook์„ ์„ ํƒํ•˜๋ฉด ์„ธ๋ถ€ ์ •๋ณด๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค: ์ด๋ฒคํŠธ, matcher, ์œ ํ˜•, ์†Œ์Šค ํŒŒ์ผ ๋ฐ ๋ช…๋ น.

3

hook ํ…Œ์ŠคํŠธ

Esc๋ฅผ ๋ˆŒ๋Ÿฌ CLI๋กœ ๋Œ์•„๊ฐ‘๋‹ˆ๋‹ค. Claude์—๊ฒŒ ๊ถŒํ•œ์ด ํ•„์š”ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก ์š”์ฒญํ•œ ๋‹ค์Œ ํ„ฐ๋ฏธ๋„์—์„œ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์Šคํฌํ†ฑ ์•Œ๋ฆผ์„ ๋ฐ›์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

Hooks๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Claude Code์˜ ๋ผ์ดํ”„์‚ฌ์ดํด์˜ ์ฃผ์š” ์ง€์ ์—์„œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ํŽธ์ง‘ ํ›„ ํŒŒ์ผ ํ˜•์‹ ์ง€์ •, ์‹คํ–‰ ์ „ ๋ช…๋ น ์ฐจ๋‹จ, Claude๊ฐ€ ์ž…๋ ฅ์ด ํ•„์š”ํ•  ๋•Œ ์•Œ๋ฆผ ์ „์†ก, ์„ธ์…˜ ์‹œ์ž‘ ์‹œ ์ปจํ…์ŠคํŠธ ์ฃผ์ž… ๋“ฑ. ์ „์ฒด hook ์ด๋ฒคํŠธ ๋ชฉ๋ก์€ Hooks ์ฐธ์กฐ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

๊ฐ ์˜ˆ์ œ์—๋Š” ์„ค์ • ํŒŒ์ผ์— ์ถ”๊ฐ€ํ•˜๋Š” ์ฆ‰์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ตฌ์„ฑ ๋ธ”๋ก์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋ณ„๋„์˜ ๋ชจ๋ธ ๊ฒ€ํ† ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์„ธ์…˜์— ๋‹ค์‹œ ํ”ผ๋“œ๋ฐฑํ•˜๋Š” hooks์˜ ํ”„๋กœ๋•์…˜ ์˜ˆ์ œ๋Š” security-guidance ํ”Œ๋Ÿฌ๊ทธ์ธ์ด Claude Code์™€ ํ†ตํ•ฉ๋˜๋Š” ๋ฐฉ์‹์„ ์ฐธ์กฐํ•˜์„ธ์š”.

Claude๊ฐ€ ์ž…๋ ฅ์ด ํ•„์š”ํ•  ๋•Œ ์•Œ๋ฆผ ๋ฐ›๊ธฐ

Claude๊ฐ€ ์ž‘์—…์„ ์™„๋ฃŒํ•˜๊ณ  ์ž…๋ ฅ์ด ํ•„์š”ํ•  ๋•Œ๋งˆ๋‹ค ๋ฐ์Šคํฌํ†ฑ ์•Œ๋ฆผ์„ ๋ฐ›์œผ๋ฏ€๋กœ ํ„ฐ๋ฏธ๋„์„ ํ™•์ธํ•˜์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ ์ž‘์—…์œผ๋กœ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด hook์€ Claude๊ฐ€ ์ž…๋ ฅ ๋˜๋Š” ๊ถŒํ•œ์„ ๊ธฐ๋‹ค๋ฆด ๋•Œ ๋ฐœ์ƒํ•˜๋Š” Notification ์ด๋ฒคํŠธ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ํƒญ์€ ํ”Œ๋žซํผ์˜ ๊ธฐ๋ณธ ์•Œ๋ฆผ ๋ช…๋ น์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ~/.claude/settings.json์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
์•Œ๋ฆผ์ด ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์œผ๋ฉด

osascript๋Š” ๊ธฐ๋ณธ ์ œ๊ณต Script Editor ์•ฑ์„ ํ†ตํ•ด ์•Œ๋ฆผ์„ ๋ผ์šฐํŒ…ํ•ฉ๋‹ˆ๋‹ค. Script Editor์— ์•Œ๋ฆผ ๊ถŒํ•œ์ด ์—†์œผ๋ฉด ๋ช…๋ น์ด ์ž๋™์œผ๋กœ ์‹คํŒจํ•˜๊ณ  macOS๋Š” ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๋„๋ก ํ”„๋กฌํ”„ํŠธํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Terminal์—์„œ ์ด๋ฅผ ํ•œ ๋ฒˆ ์‹คํ–‰ํ•˜์—ฌ Script Editor๊ฐ€ ์•Œ๋ฆผ ์„ค์ •์— ๋‚˜ํƒ€๋‚˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค:

osascript -e 'display notification "test"'

์•„์ง ์•„๋ฌด๊ฒƒ๋„ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ์„ค์ • > ์•Œ๋ฆผ์„ ์—ด๊ณ  ๋ชฉ๋ก์—์„œ Script Editor๋ฅผ ์ฐพ์€ ๋‹ค์Œ ์•Œ๋ฆผ ํ—ˆ์šฉ์„ ์ผญ๋‹ˆ๋‹ค. ๋ช…๋ น์„ ๋‹ค์‹œ ์‹คํ–‰ํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์•Œ๋ฆผ์ด ๋‚˜ํƒ€๋‚˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

๋นˆ matcher๋Š” ๋ชจ๋“  ์•Œ๋ฆผ ์œ ํ˜•์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ํŠน์ • ์ด๋ฒคํŠธ์—์„œ๋งŒ ๋ฐœ์ƒํ•˜๋„๋ก ํ•˜๋ ค๋ฉด ๋‹ค์Œ ๊ฐ’ ์ค‘ ํ•˜๋‚˜๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค:

Matcher ๋ฐœ์ƒ ์‹œ์ 
permission_prompt Claude๊ฐ€ ๋„๊ตฌ ์‚ฌ์šฉ์„ ์Šน์ธํ•˜๋„๋ก ์š”์ฒญํ•  ๋•Œ
idle_prompt Claude๊ฐ€ ์™„๋ฃŒ๋˜๊ณ  ๋‹ค์Œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ๊ธฐ๋‹ค๋ฆด ๋•Œ
auth_success ์ธ์ฆ์ด ์™„๋ฃŒ๋  ๋•Œ
elicitation_dialog MCP ์„œ๋ฒ„๊ฐ€ ์œ ๋„ ์–‘์‹์„ ์—ด ๋•Œ
elicitation_complete MCP ์œ ๋„ ์–‘์‹์ด ์ œ์ถœ๋˜๊ฑฐ๋‚˜ ๋‹ซํž ๋•Œ
elicitation_response MCP ์œ ๋„ ์‘๋‹ต์ด ์„œ๋ฒ„๋กœ ๋‹ค์‹œ ์ „์†ก๋  ๋•Œ
agent_needs_input ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์„ธ์…˜์ด ์ž…๋ ฅ์„ ๊ธฐ๋‹ค๋ฆฌ๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. agent view๊ฐ€ ์—ด๋ ค ์žˆ์„ ๋•Œ๋งŒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค
agent_completed ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์„ธ์…˜์ด ์™„๋ฃŒ๋˜๊ฑฐ๋‚˜ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. agent view๊ฐ€ ์—ด๋ ค ์žˆ์„ ๋•Œ๋งŒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค

agent_needs_input ๋ฐ agent_completed matcher๋Š” Claude Code v2.1.198 ์ด์ƒ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

/hooks๋ฅผ ์ž…๋ ฅํ•˜๊ณ  Notification์„ ์„ ํƒํ•˜์—ฌ hook์ด ๋“ฑ๋ก๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ์ด๋ฒคํŠธ ์Šคํ‚ค๋งˆ๋Š” Notification ์ฐธ์กฐ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

ํŽธ์ง‘ ํ›„ ์ฝ”๋“œ ์ž๋™ ํ˜•์‹ ์ง€์ •

Claude๊ฐ€ ํŽธ์ง‘ํ•˜๋Š” ๋ชจ๋“  ํŒŒ์ผ์—์„œ Prettier๋ฅผ ์ž๋™์œผ๋กœ ์‹คํ–‰ํ•˜์—ฌ ์ˆ˜๋™ ๊ฐœ์ž… ์—†์ด ํ˜•์‹์ด ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์ด hook์€ PostToolUse ์ด๋ฒคํŠธ๋ฅผ Edit|Write matcher์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ํŒŒ์ผ ํŽธ์ง‘ ๋„๊ตฌ ํ›„์—๋งŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๋ช…๋ น์€ jq๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŽธ์ง‘๋œ ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ถ”์ถœํ•˜๊ณ  Prettier์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์˜ .claude/settings.json์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

Claude Code v2.1.191 ์ด์ƒ์—์„œ๋Š” matcher๋ฅผ Edit,Write๋กœ๋„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฒ„์ „์—์„œ๋Š” |์™€ ,์ด ๋„๊ตฌ ์ด๋ฆ„ matcher์˜ ์ƒํ˜ธ ๊ตํ™˜ ๊ฐ€๋Šฅํ•œ ๋ชฉ๋ก ๊ตฌ๋ถ„์ž์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๋ณดํ˜ธ๋œ ํŒŒ์ผ์— ๋Œ€ํ•œ ํŽธ์ง‘ ์ฐจ๋‹จ

Claude๊ฐ€ .env, package-lock.json ๋˜๋Š” .git/์˜ ๋ชจ๋“  ํ•ญ๋ชฉ๊ณผ ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜์ง€ ๋ชปํ•˜๋„๋ก ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. Claude๋Š” ํŽธ์ง‘์ด ์ฐจ๋‹จ๋œ ์ด์œ ๋ฅผ ์„ค๋ช…ํ•˜๋Š” ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์œผ๋ฏ€๋กœ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์˜ˆ์ œ๋Š” hook์ด ํ˜ธ์ถœํ•˜๋Š” ๋ณ„๋„์˜ ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ๋Š” ๋Œ€์ƒ ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ๋ณดํ˜ธ๋œ ํŒจํ„ด ๋ชฉ๋ก๊ณผ ๋น„๊ตํ•˜๊ณ  ์ข…๋ฃŒ ์ฝ”๋“œ 2๋กœ ์ข…๋ฃŒํ•˜์—ฌ ํŽธ์ง‘์„ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค.

1

hook ์Šคํฌ๋ฆฝํŠธ ๋งŒ๋“ค๊ธฐ

์ด๋ฅผ .claude/hooks/protect-files.sh์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค:

#!/bin/bash
# protect-files.sh

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")

for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
exit 2
fi
done

exit 0
2

macOS ๋ฐ Linux์—์„œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ

Hook ์Šคํฌ๋ฆฝํŠธ๋Š” Claude Code๊ฐ€ ์‹คํ–‰ํ•˜๋ ค๋ฉด ์‹คํ–‰ ๊ฐ€๋Šฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

chmod +x .claude/hooks/protect-files.sh
3

hook ๋“ฑ๋ก

๋ชจ๋“  Edit ๋˜๋Š” Write ๋„๊ตฌ ํ˜ธ์ถœ ์ „์— ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” PreToolUse hook์„ .claude/settings.json์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}

์••์ถ• ํ›„ ์ปจํ…์ŠคํŠธ ๋‹ค์‹œ ์ฃผ์ž…

Claude์˜ ์ปจํ…์ŠคํŠธ ์œˆ๋„์šฐ๊ฐ€ ๊ฐ€๋“ ์ฐจ๋ฉด ์••์ถ•์€ ๋Œ€ํ™”๋ฅผ ์š”์•ฝํ•˜์—ฌ ๊ณต๊ฐ„์„ ํ™•๋ณดํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ค‘์š”ํ•œ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์žƒ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. compact matcher์™€ ํ•จ๊ป˜ SessionStart hook์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  ์••์ถ• ํ›„ ์ค‘์š”ํ•œ ์ปจํ…์ŠคํŠธ๋ฅผ ๋‹ค์‹œ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค.

๋ช…๋ น์ด stdout์— ์“ฐ๋Š” ๋ชจ๋“  ํ…์ŠคํŠธ๋Š” Claude์˜ ์ปจํ…์ŠคํŠธ์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ๋Š” Claude์—๊ฒŒ ํ”„๋กœ์ ํŠธ ๊ทœ์น™๊ณผ ์ตœ๊ทผ ์ž‘์—…์„ ์ƒ๊ธฐ์‹œํ‚ต๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์˜ .claude/settings.json์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"
          }
        ]
      }
    ]
  }
}

echo๋ฅผ git log --oneline -5์™€ ๊ฐ™์ด ๋™์  ์ถœ๋ ฅ์„ ์ƒ์„ฑํ•˜๋Š” ๋ชจ๋“  ๋ช…๋ น์œผ๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์„ธ์…˜ ์‹œ์ž‘ ์‹œ ์ปจํ…์ŠคํŠธ๋ฅผ ์ฃผ์ž…ํ•˜๋ ค๋ฉด CLAUDE.md ์‚ฌ์šฉ์„ ๊ณ ๋ คํ•˜์„ธ์š”. ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋Š” CLAUDE_ENV_FILE์„ ์ฐธ์กฐํ•˜์„ธ์š”.

๊ตฌ์„ฑ ๋ณ€๊ฒฝ ๊ฐ์‚ฌ

์„ธ์…˜ ์ค‘์— ์„ค์ • ๋˜๋Š” skills ํŒŒ์ผ์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋ฅผ ์ถ”์ ํ•ฉ๋‹ˆ๋‹ค. ConfigChange ์ด๋ฒคํŠธ๋Š” ์™ธ๋ถ€ ํ”„๋กœ์„ธ์Šค ๋˜๋Š” ํŽธ์ง‘๊ธฐ๊ฐ€ ๊ตฌ์„ฑ ํŒŒ์ผ์„ ์ˆ˜์ •ํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ ๊ทœ์ • ์ค€์ˆ˜๋ฅผ ์œ„ํ•ด ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๊ธฐ๋กํ•˜๊ฑฐ๋‚˜ ๋ฌด๋‹จ ์ˆ˜์ •์„ ์ฐจ๋‹จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์˜ˆ์ œ๋Š” ๊ฐ ๋ณ€๊ฒฝ์„ ๊ฐ์‚ฌ ๋กœ๊ทธ์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ~/.claude/settings.json์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

{
  "hooks": {
    "ConfigChange": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{timestamp: now | todate, source: .source, file: .file_path}' >> ~/claude-config-audit.log"
          }
        ]
      }
    ]
  }
}

Matcher๋Š” ๊ตฌ์„ฑ ์œ ํ˜•์œผ๋กœ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค: user_settings, project_settings, local_settings, policy_settings ๋˜๋Š” skills. ๋ณ€๊ฒฝ์ด ์ ์šฉ๋˜์ง€ ์•Š๋„๋ก ์ฐจ๋‹จํ•˜๋ ค๋ฉด ์ข…๋ฃŒ ์ฝ”๋“œ 2๋กœ ์ข…๋ฃŒํ•˜๊ฑฐ๋‚˜ {"decision": "block"}์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ์ž…๋ ฅ ์Šคํ‚ค๋งˆ๋Š” ConfigChange ์ฐธ์กฐ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

๋””๋ ‰ํ† ๋ฆฌ ๋˜๋Š” ํŒŒ์ผ์ด ๋ณ€๊ฒฝ๋  ๋•Œ ํ™˜๊ฒฝ ๋‹ค์‹œ ๋กœ๋“œ

์ผ๋ถ€ ํ”„๋กœ์ ํŠธ๋Š” ์–ด๋А ๋””๋ ‰ํ† ๋ฆฌ์— ์žˆ๋Š”์ง€์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. direnv์™€ ๊ฐ™์€ ๋„๊ตฌ๋Š” ์…ธ์—์„œ ์ž๋™์œผ๋กœ ์ด๋ฅผ ์ˆ˜ํ–‰ํ•˜์ง€๋งŒ Claude์˜ Bash ๋„๊ตฌ๋Š” ์ž์ฒด์ ์œผ๋กœ ํ•ด๋‹น ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์„ ํƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

SessionStart hook์„ CwdChanged hook๊ณผ ์Œ์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค. SessionStart๋Š” ์‹œ์ž‘ํ•œ ๋””๋ ‰ํ† ๋ฆฌ์— ๋Œ€ํ•œ ๋ณ€์ˆ˜๋ฅผ ๋กœ๋“œํ•˜๊ณ  CwdChanged๋Š” Claude๊ฐ€ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ๋งˆ๋‹ค ์ด๋ฅผ ๋‹ค์‹œ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค CLAUDE_ENV_FILE์— ์“ฐ๋ฉฐ, Claude Code๋Š” ๊ฐ Bash ๋ช…๋ น ์ „์— ์ด๋ฅผ ์Šคํฌ๋ฆฝํŠธ ํ”„๋ฆฌ์•ฐ๋ธ”๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ~/.claude/settings.json์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ],
    "CwdChanged": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ]
  }
}

direnv allow๋ฅผ .envrc๊ฐ€ ์žˆ๋Š” ๊ฐ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ํ•œ ๋ฒˆ ์‹คํ–‰ํ•˜์—ฌ direnv๊ฐ€ ์ด๋ฅผ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. direnv ๋Œ€์‹  devbox ๋˜๋Š” nix๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ direnv export bash ๋Œ€์‹  devbox shellenv ๋˜๋Š” devbox global shellenv๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋™์ผํ•œ ํŒจํ„ด์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“  ๋””๋ ‰ํ† ๋ฆฌ ๋ณ€๊ฒฝ์ด ์•„๋‹Œ ํŠน์ • ํŒŒ์ผ์— ๋ฐ˜์‘ํ•˜๋ ค๋ฉด FileChanged๋ฅผ matcher์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ์‹œํ•  ํŒŒ์ผ ์ด๋ฆ„์„ ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค (ํŒŒ์ดํ”„๋กœ ๊ตฌ๋ถ„). ๊ฐ์‹œ ๋ชฉ๋ก์„ ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์ด ๊ฐ’์€ ์ •๊ทœ์‹์œผ๋กœ ํ‰๊ฐ€๋˜์ง€ ์•Š๊ณ  ๋ฆฌํ„ฐ๋Ÿด ํŒŒ์ผ ์ด๋ฆ„์œผ๋กœ ๋ถ„ํ• ๋ฉ๋‹ˆ๋‹ค. FileChanged๋ฅผ ์ฐธ์กฐํ•˜์—ฌ ํŒŒ์ผ์ด ๋ณ€๊ฒฝ๋  ๋•Œ ์–ด๋–ค hook ๊ทธ๋ฃน์ด ์‹คํ–‰๋˜๋Š”์ง€ ํ•„ํ„ฐ๋งํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ํ™•์ธํ•˜์„ธ์š”. ์ด ์˜ˆ์ œ๋Š” ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ์—์„œ .envrc ๋ฐ .env๋ฅผ ๊ฐ์‹œํ•ฉ๋‹ˆ๋‹ค:

{
  "hooks": {
    "FileChanged": [
      {
        "matcher": ".envrc|.env",
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ]
  }
}

์ž…๋ ฅ ์Šคํ‚ค๋งˆ, watchPaths ์ถœ๋ ฅ ๋ฐ CLAUDE_ENV_FILE ์„ธ๋ถ€ ์ •๋ณด๋Š” CwdChanged ๋ฐ FileChanged ์ฐธ์กฐ ํ•ญ๋ชฉ์„ ์ฐธ์กฐํ•˜์„ธ์š”.

ํŠน์ • ๊ถŒํ•œ ํ”„๋กฌํ”„ํŠธ ์ž๋™ ์Šน์ธ

ํ•ญ์ƒ ํ—ˆ์šฉํ•˜๋Š” ๋„๊ตฌ ํ˜ธ์ถœ์— ๋Œ€ํ•œ ์Šน์ธ ๋Œ€ํ™”๋ฅผ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ๋Š” ExitPlanMode๋ฅผ ์ž๋™ ์Šน์ธํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” Claude๊ฐ€ ๊ณ„ํš ์ œ์‹œ๋ฅผ ์™„๋ฃŒํ•˜๊ณ  ์ง„ํ–‰์„ ์š”์ฒญํ•  ๋•Œ ํ˜ธ์ถœํ•˜๋Š” ๋„๊ตฌ์ด๋ฏ€๋กœ ๊ณ„ํš์ด ์ค€๋น„๋  ๋•Œ๋งˆ๋‹ค ํ”„๋กฌํ”„ํŠธ๊ฐ€ ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์œ„์˜ ์ข…๋ฃŒ ์ฝ”๋“œ ์˜ˆ์ œ์™€ ๋‹ฌ๋ฆฌ ์ž๋™ ์Šน์ธ์„ ์œ„ํ•ด์„œ๋Š” hook์ด JSON ๊ฒฐ์ •์„ stdout์— ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. PermissionRequest hook์€ Claude Code๊ฐ€ ๊ถŒํ•œ ๋Œ€ํ™”๋ฅผ ํ‘œ์‹œํ•˜๋ ค๊ณ  ํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋ฉฐ "behavior": "allow"๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์‚ฌ์šฉ์ž ๋Œ€์‹  ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค.

Matcher๋Š” hook์„ ExitPlanMode๋กœ๋งŒ ๋ฒ”์œ„๋ฅผ ์ง€์ •ํ•˜๋ฏ€๋กœ ๋‹ค๋ฅธ ํ”„๋กฌํ”„ํŠธ๋Š” ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ~/.claude/settings.json์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "ExitPlanMode",
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
          }
        ]
      }
    ]
  }
}

Hook์ด ์Šน์ธํ•˜๋ฉด Claude Code๋Š” ๊ณ„ํš ๋ชจ๋“œ๋ฅผ ์ข…๋ฃŒํ•˜๊ณ  ๊ณ„ํš ๋ชจ๋“œ์— ๋“ค์–ด๊ฐ€๊ธฐ ์ „์— ํ™œ์„ฑํ™”๋˜์—ˆ๋˜ ๊ถŒํ•œ ๋ชจ๋“œ๋ฅผ ๋ณต์›ํ•ฉ๋‹ˆ๋‹ค. ํŠธ๋žœ์Šคํฌ๋ฆฝํŠธ๋Š” ๋Œ€ํ™”๊ฐ€ ๋‚˜ํƒ€๋‚ฌ์„ ์œ„์น˜์— "Allowed by PermissionRequest hook"์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. Hook ๊ฒฝ๋กœ๋Š” ํ•ญ์ƒ ํ˜„์žฌ ๋Œ€ํ™”๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค: ๋Œ€ํ™”๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ์‹์œผ๋กœ ์ปจํ…์ŠคํŠธ๋ฅผ ์ง€์šฐ๊ณ  ์ƒˆ๋กœ์šด ๊ตฌํ˜„ ์„ธ์…˜์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋Œ€์‹  ํŠน์ • ๊ถŒํ•œ ๋ชจ๋“œ๋ฅผ ์„ค์ •ํ•˜๋ ค๋ฉด hook์˜ ์ถœ๋ ฅ์— setMode ํ•ญ๋ชฉ์ด ์žˆ๋Š” updatedPermissions ๋ฐฐ์—ด์ด ํฌํ•จ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. mode ๊ฐ’์€ default, acceptEdits ๋˜๋Š” bypassPermissions๊ณผ ๊ฐ™์€ ๋ชจ๋“  ๊ถŒํ•œ ๋ชจ๋“œ์ด๋ฉฐ destination: "session"์€ ํ˜„์žฌ ์„ธ์…˜์—๋งŒ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

์„ธ์…˜์„ acceptEdits๋กœ ์ „ํ™˜ํ•˜๋ ค๋ฉด hook์ด ์ด JSON์„ stdout์— ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedPermissions": [
        { "type": "setMode", "mode": "acceptEdits", "destination": "session" }
      ]
    }
  }
}

Matcher๋ฅผ ๊ฐ€๋Šฅํ•œ ํ•œ ์ข๊ฒŒ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. .*์™€ ์ผ์น˜ํ•˜๊ฑฐ๋‚˜ matcher๋ฅผ ๋น„์›Œ๋‘๋ฉด ํŒŒ์ผ ์“ฐ๊ธฐ ๋ฐ ์…ธ ๋ช…๋ น์„ ํฌํ•จํ•œ ๋ชจ๋“  ๊ถŒํ•œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž๋™ ์Šน์ธํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ๊ฒฐ์ • ํ•„๋“œ ์ง‘ํ•ฉ์€ PermissionRequest ์ฐธ์กฐ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

Hooks ์ž‘๋™ ๋ฐฉ์‹

Hook ์ด๋ฒคํŠธ๋Š” Claude Code์˜ ๋ผ์ดํ”„์‚ฌ์ดํด์˜ ํŠน์ • ์ง€์ ์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ผ์น˜ํ•˜๋Š” ๋ชจ๋“  hooks๊ฐ€ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰๋˜๊ณ  ๋™์ผํ•œ hook ๋ช…๋ น์€ ์ž๋™์œผ๋กœ ์ค‘๋ณต ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ํ‘œ๋Š” ๊ฐ ์ด๋ฒคํŠธ์™€ ๋ฐœ์ƒ ์‹œ๊ธฐ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค:

Event When it fires
SessionStart When a session begins or resumes
Setup When you start Claude Code with --init-only, or with --init or --maintenance in -p mode. For one-time preparation in CI or scripts
UserPromptSubmit When you submit a prompt, before Claude processes it
UserPromptExpansion When a user-typed command expands into a prompt, before it reaches Claude. Can block the expansion
PreToolUse Before a tool call executes. Can block it
PermissionRequest When a permission dialog appears
PermissionDenied When a tool call is denied by the auto mode classifier. Return {retry: true} to tell the model it may retry the denied tool call
PostToolUse After a tool call succeeds
PostToolUseFailure After a tool call fails
PostToolBatch After a full batch of parallel tool calls resolves, before the next model call
Notification When Claude Code sends a notification
MessageDisplay While assistant message text is displayed
SubagentStart When a subagent is spawned
SubagentStop When a subagent finishes
TaskCreated When a task is being created via TaskCreate
TaskCompleted When a task is being marked as completed
Stop When Claude finishes responding
StopFailure When the turn ends due to an API error. Output and exit code are ignored
TeammateIdle When an agent team teammate is about to go idle
InstructionsLoaded When a CLAUDE.md or .claude/rules/*.md file is loaded into context. Fires at session start and when files are lazily loaded during a session
ConfigChange When a configuration file changes during a session
CwdChanged When the working directory changes, for example when Claude executes a cd command. Useful for reactive environment management with tools like direnv
FileChanged When a watched file changes on disk. The matcher field specifies which filenames to watch
WorktreeCreate When a worktree is being created via --worktree, isolation: "worktree", or for a background session. Replaces default git behavior
WorktreeRemove When a worktree is being removed at session exit, when a subagent finishes, or when you delete a background session
PreCompact Before context compaction
PostCompact After context compaction completes
Elicitation When an MCP server requests user input during a tool call
ElicitationResult After a user responds to an MCP elicitation, before the response is sent back to the server
SessionEnd When a session terminates

๊ฐ hook์—๋Š” ์‹คํ–‰ ๋ฐฉ์‹์„ ๊ฒฐ์ •ํ•˜๋Š” type์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ hooks๋Š” "type": "command"๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์…ธ ๋ช…๋ น์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋„ค ๊ฐ€์ง€ ๋‹ค๋ฅธ ์œ ํ˜•์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • "type": "http": ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ URL์— POSTํ•ฉ๋‹ˆ๋‹ค. HTTP hooks๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.
  • "type": "mcp_tool": ์ด๋ฏธ ์—ฐ๊ฒฐ๋œ MCP ์„œ๋ฒ„์—์„œ ๋„๊ตฌ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. MCP tool hooks๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.
  • "type": "prompt": ๋‹จ์ผ ํ„ด LLM ํ‰๊ฐ€์ž…๋‹ˆ๋‹ค. ํ”„๋กฌํ”„ํŠธ ๊ธฐ๋ฐ˜ hooks๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.
  • "type": "agent": ๋„๊ตฌ ์•ก์„ธ์Šค๋ฅผ ํ†ตํ•œ ๋‹ค์ค‘ ํ„ด ๊ฒ€์ฆ์ž…๋‹ˆ๋‹ค. ์—์ด์ „ํŠธ hooks๋Š” ์‹คํ—˜์ ์ด๋ฉฐ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—์ด์ „ํŠธ ๊ธฐ๋ฐ˜ hooks๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

์—ฌ๋Ÿฌ hooks์˜ ๊ฒฐ๊ณผ ๊ฒฐํ•ฉ

์—ฌ๋Ÿฌ hooks๊ฐ€ ๋™์ผํ•œ ์ด๋ฒคํŠธ์™€ ์ผ์น˜ํ•˜๋ฉด ๋ชจ๋“  hook์˜ ๋ช…๋ น์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์‹คํ–‰๋œ ํ›„ Claude Code๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ๋ณ‘ํ•ฉํ•ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ hook์ด deny๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ํ˜•์ œ hooks์˜ ์‹คํ–‰์„ ์ค‘์ง€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•œ hook์˜ deny๊ฐ€ ๋‹ค๋ฅธ hook์˜ ๋ถ€์ž‘์šฉ์„ ์–ต์ œํ•˜๋Š” ๊ฒƒ์— ์˜์กดํ•˜์ง€ ๋งˆ์„ธ์š”.

๋ชจ๋“  ์ผ์น˜ํ•˜๋Š” hooks๊ฐ€ ์™„๋ฃŒ๋œ ํ›„ Claude Code๋Š” ์ถœ๋ ฅ์„ ๊ฒฐํ•ฉํ•ฉ๋‹ˆ๋‹ค. PreToolUse ๊ถŒํ•œ ๊ฒฐ์ •์˜ ๊ฒฝ์šฐ ๊ฐ€์žฅ ์ œํ•œ์ ์ธ ๋‹ต๋ณ€์ด ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค: deny, defer, ask, allow ์ˆœ์„œ์ž…๋‹ˆ๋‹ค. additionalContext์˜ ํ…์ŠคํŠธ๋Š” ๋ชจ๋“  hook์—์„œ ์œ ์ง€๋˜๊ณ  Claude์™€ ํ•จ๊ป˜ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.

์•„๋ž˜ ์˜ˆ์ œ๋Š” Bash์— ๋‘ ๊ฐœ์˜ PreToolUse hooks๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ๋Š” ๋ชจ๋“  ๋ช…๋ น์„ ๋กœ๊ทธ ํŒŒ์ผ์— ์ถ”๊ฐ€ํ•˜๊ณ  0์œผ๋กœ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ๋Š” ๋ช…๋ น์— rm -rf๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์„ ๋•Œ ๊ฑฐ๋ถ€ํ•˜๊ธฐ ์œ„ํ•ด 2๋กœ ์ข…๋ฃŒํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r .tool_input.command >> ~/.claude/bash.log"
          },
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm-rf.sh"
          }
        ]
      }
    ]
  }
}

Claude๊ฐ€ rm -rf /tmp/build๋ฅผ ์‹คํ–‰ํ•˜๋ ค๊ณ  ํ•  ๋•Œ ๋‘ hooks ๋ชจ๋‘ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๋กœ๊น… hook์€ ๋ช…๋ น์„ ~/.claude/bash.log์— ์“ฐ๊ณ  0์œผ๋กœ ์ข…๋ฃŒํ•˜์—ฌ ๊ฒฐ์ •์ด ์—†์Œ์„ ๋ณด๊ณ ํ•ฉ๋‹ˆ๋‹ค. ๋ณด์•ˆ ๊ทœ์น™ hook์€ 2๋กœ ์ข…๋ฃŒํ•˜์—ฌ ๋„๊ตฌ ํ˜ธ์ถœ์„ ๊ฑฐ๋ถ€ํ•ฉ๋‹ˆ๋‹ค. ๊ฑฐ๋ถ€๊ฐ€ ์šฐ์„ ํ•˜๋ฏ€๋กœ Claude Code๋Š” ๋ช…๋ น์„ ์ฐจ๋‹จํ•˜๊ณ  Claude์—๊ฒŒ ๋ณด์•ˆ ๊ทœ์น™์˜ stderr๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๋กœ๊น… hook์ด ์ด๋ฏธ ์‹คํ–‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋กœ๊ทธ ํ•ญ๋ชฉ์€ ์—ฌ์ „ํžˆ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.

์ž…๋ ฅ ์ฝ๊ธฐ ๋ฐ ์ถœ๋ ฅ ๋ฐ˜ํ™˜

Hooks๋Š” stdin, stdout, stderr ๋ฐ ์ข…๋ฃŒ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด Claude Code์™€ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด Claude Code๋Š” ์ด๋ฒคํŠธ๋ณ„ ๋ฐ์ดํ„ฐ๋ฅผ JSON์œผ๋กœ ์Šคํฌ๋ฆฝํŠธ์˜ stdin์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ๋Š” ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ  ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ ๋‹ค์Œ ์ข…๋ฃŒ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด Claude Code์— ๋‹ค์Œ ์ž‘์—…์„ ์•Œ๋ฆฝ๋‹ˆ๋‹ค.

Hook ์ž…๋ ฅ

๋ชจ๋“  ์ด๋ฒคํŠธ์—๋Š” session_id ๋ฐ cwd์™€ ๊ฐ™์€ ๊ณตํ†ต ํ•„๋“œ๊ฐ€ ํฌํ•จ๋˜์ง€๋งŒ ๊ฐ ์ด๋ฒคํŠธ ์œ ํ˜•์€ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด Claude๊ฐ€ Bash ๋ช…๋ น์„ ์‹คํ–‰ํ•  ๋•Œ PreToolUse hook์€ stdin์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค:

{
  "session_id": "abc123",          // ์ด ์„ธ์…˜์˜ ๊ณ ์œ  ID
  "cwd": "/Users/sarah/myproject", // ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ์˜ ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ
  "hook_event_name": "PreToolUse", // ์ด hook์„ ํŠธ๋ฆฌ๊ฑฐํ•œ ์ด๋ฒคํŠธ
  "tool_name": "Bash",             // Claude๊ฐ€ ์‚ฌ์šฉํ•˜๋ ค๋Š” ๋„๊ตฌ
  "tool_input": {                  // Claude๊ฐ€ ๋„๊ตฌ์— ์ „๋‹ฌํ•œ ์ธ์ˆ˜
    "command": "npm test"          // Bash์˜ ๊ฒฝ์šฐ ์ด๊ฒƒ์€ ์…ธ ๋ช…๋ น์ž…๋‹ˆ๋‹ค
  }
}

์Šคํฌ๋ฆฝํŠธ๋Š” ํ•ด๋‹น JSON์„ ๊ตฌ๋ฌธ ๋ถ„์„ํ•˜๊ณ  ํ•ด๋‹น ํ•„๋“œ์— ๋Œ€ํ•ด ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. UserPromptSubmit hooks๋Š” prompt ํ…์ŠคํŠธ๋ฅผ ๋Œ€์‹  ๋ฐ›๊ณ , SessionStart hooks๋Š” source (startup, resume, clear, compact)๋ฅผ ๋ฐ›์œผ๋ฉฐ, ๋“ฑ๋“ฑ์ž…๋‹ˆ๋‹ค. ๊ณต์œ  ํ•„๋“œ๋Š” ์ฐธ์กฐ์˜ ๊ณตํ†ต ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ์ฐธ์กฐํ•˜๊ณ  ๊ฐ ์ด๋ฒคํŠธ๋ณ„ ์„น์…˜์—์„œ ์ด๋ฒคํŠธ๋ณ„ ์Šคํ‚ค๋งˆ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

Hook ์ถœ๋ ฅ

์Šคํฌ๋ฆฝํŠธ๋Š” stdout ๋˜๋Š” stderr์— ์“ฐ๊ณ  ํŠน์ • ์ฝ”๋“œ๋กœ ์ข…๋ฃŒํ•˜์—ฌ Claude Code์— ๋‹ค์Œ ์ž‘์—…์„ ์•Œ๋ฆฝ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ช…๋ น์„ ์ฐจ๋‹จํ•˜๋ ค๋Š” PreToolUse hook:

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

if echo "$COMMAND" | grep -q "drop table"; then
  echo "Blocked: dropping tables is not allowed" >&2  # stderr๋Š” Claude์˜ ํ”ผ๋“œ๋ฐฑ์ด ๋ฉ๋‹ˆ๋‹ค
  exit 2 # exit 2 = ์ž‘์—… ์ฐจ๋‹จ
fi

exit 0  # exit 0 = ์ง„ํ–‰ ํ—ˆ์šฉ

์ข…๋ฃŒ ์ฝ”๋“œ๋Š” ๋‹ค์Œ์— ์ผ์–ด๋‚  ์ผ์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค:

  • Exit 0: hook์€ ์ด์˜๋ฅผ ์ œ๊ธฐํ•˜์ง€ ์•Š์œผ๋ฉฐ ์ž‘์—…์ด ์ •์ƒ์ ์œผ๋กœ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. PreToolUse hook์˜ ๊ฒฝ์šฐ ์ด๊ฒƒ์€ ๋„๊ตฌ ํ˜ธ์ถœ์„ ์Šน์ธํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: ์ •์ƒ์ ์ธ ๊ถŒํ•œ ํ๋ฆ„์ด ์—ฌ์ „ํžˆ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. UserPromptSubmit, UserPromptExpansion ๋ฐ SessionStart hooks์˜ ๊ฒฝ์šฐ stdout์— ์“ฐ๋Š” ๋ชจ๋“  ๊ฒƒ์ด Claude์˜ ์ปจํ…์ŠคํŠธ์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.
  • Exit 2: ์ž‘์—…์ด ์ฐจ๋‹จ๋ฉ๋‹ˆ๋‹ค. stderr์— ์ด์œ ๋ฅผ ์“ฐ๋ฉด Claude๊ฐ€ ํ”ผ๋“œ๋ฐฑ์œผ๋กœ ๋ฐ›์•„ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ถ€ ์ด๋ฒคํŠธ๋Š” ์ฐจ๋‹จ๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: SessionStart, Setup, Notification ๋ฐ ๊ธฐํƒ€์˜ ๊ฒฝ์šฐ exit 2๋Š” stderr๋ฅผ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œํ•˜๊ณ  ์‹คํ–‰์ด ๊ณ„์†๋ฉ๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ๋ณ„ ์ „์ฒด ๋ชฉ๋ก์€ ์ด๋ฒคํŠธ๋ณ„ exit ์ฝ”๋“œ 2 ๋™์ž‘์„ ์ฐธ์กฐํ•˜์„ธ์š”.
  • ๋‹ค๋ฅธ ์ข…๋ฃŒ ์ฝ”๋“œ: ์ž‘์—…์ด ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. ํŠธ๋žœ์Šคํฌ๋ฆฝํŠธ๋Š” <hook name> hook error ๊ณต์ง€๋ฅผ ํ‘œ์‹œํ•œ ํ›„ stderr์˜ ์ฒซ ๋ฒˆ์งธ ์ค„์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด stderr๋Š” ๋””๋ฒ„๊ทธ ๋กœ๊ทธ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

๊ตฌ์กฐํ™”๋œ JSON ์ถœ๋ ฅ

์ข…๋ฃŒ ์ฝ”๋“œ๋Š” ์ฐจ๋‹จํ•˜๊ฑฐ๋‚˜ ์นจ๋ฌตํ•˜๋Š” ๊ฒƒ๋งŒ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋” ๋งŽ์€ ์ œ์–ด๋ฅผ ์œ„ํ•ด exit 0์„ ํ•˜๊ณ  stdout์— JSON ๊ฐ์ฒด๋ฅผ ์ธ์‡„ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด PreToolUse hook์€ ๋„๊ตฌ ํ˜ธ์ถœ์„ ๊ฑฐ๋ถ€ํ•˜๊ณ  ์ด์œ ๋ฅผ ์•Œ๋ฆฌ๊ฑฐ๋‚˜ ์‚ฌ์šฉ์ž ์Šน์ธ์„ ์œ„ํ•ด ์—์Šค์ปฌ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Use rg instead of grep for better performance"
  }
}

"deny"๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Claude Code๋Š” ๋„๊ตฌ ํ˜ธ์ถœ์„ ์ทจ์†Œํ•˜๊ณ  permissionDecisionReason์„ Claude์—๊ฒŒ ํ”ผ๋“œ๋ฐฑ์œผ๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ด permissionDecision ๊ฐ’์€ PreToolUse์—๋งŒ ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค:

  • "allow": ๋Œ€ํ™”ํ˜• ๊ถŒํ•œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ๊ฑฐ๋ถ€ ๋ฐ ์š”์ฒญ ๊ทœ์น™์„ ํฌํ•จํ•œ ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ๊ด€๋ฆฌํ˜• ๊ฑฐ๋ถ€ ๋ชฉ๋ก์€ ์—ฌ์ „ํžˆ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์กฐ์ง์ด ask๋กœ ์„ค์ •ํ•œ ์ปค๋„ฅํ„ฐ ๋„๊ตฌ ๋ฐ requiresUserInteraction์œผ๋กœ ํ‘œ์‹œ๋œ MCP ๋„๊ตฌ์— ๋Œ€ํ•œ ํ”„๋กฌํ”„ํŠธ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.
  • "deny": ๋„๊ตฌ ํ˜ธ์ถœ์„ ์ทจ์†Œํ•˜๊ณ  ์ด์œ ๋ฅผ Claude์— ์ „์†กํ•ฉ๋‹ˆ๋‹ค
  • "ask": ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ถŒํ•œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค

๋„ค ๋ฒˆ์งธ ๊ฐ’์ธ "defer"๋Š” -p ํ”Œ๋ž˜๊ทธ๊ฐ€ ์žˆ๋Š” ๋น„๋Œ€ํ™”ํ˜• ๋ชจ๋“œ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๋„๊ตฌ ํ˜ธ์ถœ์„ ๋ณด์กดํ•˜์—ฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ข…๋ฃŒํ•˜๋ฏ€๋กœ Agent SDK ๋ž˜ํผ๊ฐ€ ์ž…๋ ฅ์„ ์ˆ˜์ง‘ํ•˜๊ณ  ์žฌ๊ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฐธ์กฐ์˜ ๋‚˜์ค‘์— ๋„๊ตฌ ํ˜ธ์ถœ ์—ฐ๊ธฐ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

"allow"๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋Œ€ํ™”ํ˜• ํ”„๋กฌํ”„ํŠธ๋ฅผ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ถŒํ•œ ๊ทœ์น™์„ ์žฌ์ •์˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ฑฐ๋ถ€ ๊ทœ์น™์ด ๋„๊ตฌ ํ˜ธ์ถœ๊ณผ ์ผ์น˜ํ•˜๋ฉด hook์ด "allow"๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋”๋ผ๋„ ํ˜ธ์ถœ์ด ์ฐจ๋‹จ๋ฉ๋‹ˆ๋‹ค. ์š”์ฒญ ๊ทœ์น™์ด ์ผ์น˜ํ•˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์—ฌ์ „ํžˆ ํ”„๋กฌํ”„ํŠธ๋ฉ๋‹ˆ๋‹ค. ์กฐ์ง์ด ask๋กœ ์„ค์ •ํ•œ ์ปค๋„ฅํ„ฐ ๋„๊ตฌ ๋ฐ requiresUserInteraction์œผ๋กœ ํ‘œ์‹œ๋œ MCP ๋„๊ตฌ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. ์ด๋Š” ๊ด€๋ฆฌํ˜• ์„ค์ •์„ ํฌํ•จํ•œ ๋ชจ๋“  ์„ค์ • ๋ฒ”์œ„์˜ ๊ฑฐ๋ถ€ ๊ทœ์น™์ด hook ์Šน์ธ๋ณด๋‹ค ํ•ญ์ƒ ์šฐ์„ ํ•œ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.

๋‹ค๋ฅธ ์ด๋ฒคํŠธ๋Š” ๋‹ค๋ฅธ ๊ฒฐ์ • ํŒจํ„ด์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด PostToolUse ๋ฐ Stop hooks๋Š” ์ตœ์ƒ์œ„ decision: "block" ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  PermissionRequest๋Š” hookSpecificOutput.decision.behavior๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ๋ณ„ ์ „์ฒด ๋ถ„์„์€ ์ฐธ์กฐ์˜ ์š”์•ฝ ํ‘œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

UserPromptSubmit hooks์˜ ๊ฒฝ์šฐ hookSpecificOutput.additionalContext๋ฅผ ๋Œ€์‹  ์‚ฌ์šฉํ•˜์—ฌ Claude์˜ ์ปจํ…์ŠคํŠธ์— ํ…์ŠคํŠธ๋ฅผ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค. additionalContext๋ฅผ hookSpecificOutput ๋‚ด์— ์ค‘์ฒฉํ•˜์„ธ์š”. JSON์˜ ์ตœ์ƒ์œ„ ์ˆ˜์ค€์— ๋ฐฐ์น˜ํ•˜๋ฉด Claude Code๋Š” ์ด๋ฅผ ์ž๋™์œผ๋กœ ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ด ์ถœ๋ ฅ์€ ๋ชจ๋“  ํ”„๋กฌํ”„ํŠธ์— ํ˜„์žฌ ๋ธŒ๋žœ์น˜ ์ƒํƒœ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

{
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "Current branch: release-42. Deploy freeze until Friday."
  }
}

ํ”„๋กฌํ”„ํŠธ ์ฐจ๋‹จ ๋ฐ ์„ธ์…˜ ์ œ๋ชฉ ์„ค์ •์„ ํฌํ•จํ•œ ์ „์ฒด ์ถœ๋ ฅ ํ˜•ํƒœ๋Š” UserPromptSubmit ๊ฒฐ์ • ์ œ์–ด๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

type: "prompt"๋ฅผ ์‚ฌ์šฉํ•˜๋Š” Hooks๋Š” ์ถœ๋ ฅ์„ ๋‹ค๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค: ํ”„๋กฌํ”„ํŠธ ๊ธฐ๋ฐ˜ hooks๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

Matchers๋กœ hooks ํ•„ํ„ฐ๋ง

Matcher๊ฐ€ ์—†์œผ๋ฉด hook์€ ์ด๋ฒคํŠธ์˜ ๋ชจ๋“  ๋ฐœ์ƒ์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. Matchers๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฒ”์œ„๋ฅผ ์ขํž ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ชจ๋“  ๋„๊ตฌ ํ˜ธ์ถœ ํ›„๊ฐ€ ์•„๋‹Œ ํŒŒ์ผ ํŽธ์ง‘ ํ›„์—๋งŒ ํฌ๋งคํ„ฐ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด PostToolUse hook์— matcher๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "prettier --write ..." }
        ]
      }
    ]
  }
}

"Edit|Write" matcher๋Š” Claude๊ฐ€ Edit ๋˜๋Š” Write ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋งŒ ๋ฐœ์ƒํ•˜๊ณ  Bash, Read ๋˜๋Š” ๋‹ค๋ฅธ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Claude Code v2.1.191 ์ด์ƒ์—์„œ๋Š” ์‰ผํ‘œ๋„ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋Œ€์•ˆ์„ ๊ตฌ๋ถ„ํ•˜๋ฏ€๋กœ "Edit, Write"๋Š” ๋™๋“ฑํ•ฉ๋‹ˆ๋‹ค. Matcher ํŒจํ„ด์„ ์ฐธ์กฐํ•˜์—ฌ ์ผ๋ฐ˜ ์ด๋ฆ„๊ณผ ์ •๊ทœ์‹์ด ํ‰๊ฐ€๋˜๋Š” ๋ฐฉ์‹์„ ํ™•์ธํ•˜์„ธ์š”.

๊ฐ ์ด๋ฒคํŠธ ์œ ํ˜•์€ ํŠน์ • ํ•„๋“œ์—์„œ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค:

์ด๋ฒคํŠธ Matcher๊ฐ€ ํ•„ํ„ฐ๋งํ•˜๋Š” ๊ฒƒ ์˜ˆ์ œ matcher ๊ฐ’
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied ๋„๊ตฌ ์ด๋ฆ„ Bash, Edit|Write, mcp__.*
SessionStart ์„ธ์…˜์ด ์‹œ์ž‘๋œ ๋ฐฉ์‹ startup, resume, clear, compact
Setup ์–ด๋–ค CLI ํ”Œ๋ž˜๊ทธ๊ฐ€ ์„ค์ •์„ ํŠธ๋ฆฌ๊ฑฐํ–ˆ๋Š”์ง€ init, maintenance
SessionEnd ์„ธ์…˜์ด ์ข…๋ฃŒ๋œ ์ด์œ  clear, resume, logout, prompt_input_exit, bypass_permissions_disabled, other
Notification ์•Œ๋ฆผ ์œ ํ˜• permission_prompt, idle_prompt, auth_success, elicitation_dialog, elicitation_complete, elicitation_response, agent_needs_input, agent_completed
SubagentStart ์—์ด์ „ํŠธ ์œ ํ˜• general-purpose, Explore, Plan ๋˜๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ์—์ด์ „ํŠธ ์ด๋ฆ„
PreCompact, PostCompact ์••์ถ•์„ ํŠธ๋ฆฌ๊ฑฐํ•œ ๊ฒƒ manual, auto
SubagentStop ์—์ด์ „ํŠธ ์œ ํ˜• SubagentStart์™€ ๋™์ผํ•œ ๊ฐ’
ConfigChange ๊ตฌ์„ฑ ์†Œ์Šค user_settings, project_settings, local_settings, policy_settings, skills
StopFailure ์˜ค๋ฅ˜ ์œ ํ˜• rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, unknown
InstructionsLoaded ๋กœ๋“œ ์ด์œ  session_start, nested_traversal, path_glob_match, include, compact
Elicitation MCP ์„œ๋ฒ„ ์ด๋ฆ„ ๊ตฌ์„ฑ๋œ MCP ์„œ๋ฒ„ ์ด๋ฆ„
ElicitationResult MCP ์„œ๋ฒ„ ์ด๋ฆ„ Elicitation๊ณผ ๋™์ผํ•œ ๊ฐ’
FileChanged ๊ฐ์‹œํ•  ๋ฆฌํ„ฐ๋Ÿด ํŒŒ์ผ ์ด๋ฆ„ (FileChanged ์ฐธ์กฐ) .envrc|.env
UserPromptExpansion ๋ช…๋ น ์ด๋ฆ„ skill ๋˜๋Š” ๋ช…๋ น ์ด๋ฆ„
UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, CwdChanged, MessageDisplay matcher ์ง€์› ์—†์Œ ๋ชจ๋“  ๋ฐœ์ƒ์—์„œ ํ•ญ์ƒ ๋ฐœ์ƒ

๋‹ค์–‘ํ•œ ์ด๋ฒคํŠธ ์œ ํ˜•์—์„œ matchers๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋ช‡ ๊ฐ€์ง€ ์ถ”๊ฐ€ ์˜ˆ์ œ:

Bash ๋„๊ตฌ ํ˜ธ์ถœ๋งŒ ์ผ์น˜์‹œํ‚ค๊ณ  ๊ฐ ๋ช…๋ น์„ ํŒŒ์ผ์— ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. PostToolUse ์ด๋ฒคํŠธ๋Š” ๋ช…๋ น์ด ์™„๋ฃŒ๋œ ํ›„ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ tool_input.command๋Š” ์‹คํ–‰๋œ ๋‚ด์šฉ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. Hook์€ stdin์—์„œ ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ JSON์œผ๋กœ ๋ฐ›๊ณ  jq -r '.tool_input.command'๋Š” ๋ช…๋ น ๋ฌธ์ž์—ด๋งŒ ์ถ”์ถœํ•˜๋ฉฐ >>๋Š” ๋กœ๊ทธ ํŒŒ์ผ์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt"
}
]
}
]
}
}

์ „์ฒด matcher ๊ตฌ๋ฌธ์€ Hooks ์ฐธ์กฐ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

๋„๊ตฌ ์ด๋ฆ„ ๋ฐ ์ธ์ˆ˜๋กœ `if` ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„ํ„ฐ๋ง

if ํ•„๋“œ๋Š” ๊ถŒํ•œ ๊ทœ์น™ ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋„๊ตฌ ์ด๋ฆ„๊ณผ ์ธ์ˆ˜๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ hooks๋ฅผ ํ•„ํ„ฐ๋งํ•˜๋ฏ€๋กœ hook ํ”„๋กœ์„ธ์Šค๋Š” ๋„๊ตฌ ํ˜ธ์ถœ์ด ์ผ์น˜ํ•  ๋•Œ๋งŒ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋„๊ตฌ ์ด๋ฆ„๋งŒ์œผ๋กœ ๊ทธ๋ฃน ์ˆ˜์ค€์—์„œ ํ•„ํ„ฐ๋งํ•˜๋Š” matcher๋ฅผ ์ดˆ๊ณผํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ๋ชจ๋“  Bash ๋ช…๋ น์ด ์•„๋‹Œ git ๋ช…๋ น์„ ์‚ฌ์šฉํ•  ๋•Œ๋งŒ hook์„ ์‹คํ–‰ํ•˜๋ ค๋ฉด:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(git *)",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh"
          }
        ]
      }
    ]
  }
}

Hook ๋ช…๋ น์ด ์‹คํ–‰๋˜๋Š”์ง€ ์—ฌ๋ถ€๋Š” if ํŒจํ„ด์˜ ํ˜•ํƒœ์™€ Claude๊ฐ€ ํ˜ธ์ถœํ•˜๋Š” Bash ๋ช…๋ น์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค:

if ํŒจํ„ด Bash ๋ช…๋ น Hook์ด ์‹คํ–‰๋˜๋‚˜์š”? ์ด์œ 
Bash(git *) git push ์˜ˆ ๋ช…๋ น ์ด๋ฆ„์ด ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค
Bash(git *) npm test && git push ์˜ˆ ๊ฐ ์„œ๋ธŒ๋ช…๋ น์ด ํ™•์ธ๋ฉ๋‹ˆ๋‹ค; git push๊ฐ€ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค
Bash(git *) echo $(git log) ์˜ˆ $() ๋ฐ ๋ฐฑํ‹ฑ ๋‚ด์˜ ๋ช…๋ น์ด ํ™•์ธ๋ฉ๋‹ˆ๋‹ค; git log๊ฐ€ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค
Bash(git *) echo $(date) ์•„๋‹ˆ์˜ค ์„œ๋ธŒ๋ช…๋ น์ด git *๊ณผ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค
Bash(git push *) echo $(date) ์˜ˆ ๋ช…๋ น ์ด๋ฆ„๋ณด๋‹ค ๋” ๋งŽ์ด ์ง€์ •ํ•˜๋Š” ํŒจํ„ด์€ $(), ๋ฐฑํ‹ฑ ๋˜๋Š” $VAR์—์„œ ์–ด์จŒ๋“  hook์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค

ํ•„ํ„ฐ๋Š” ๋˜ํ•œ ์‹คํŒจ ์‹œ ์—ด๋ ค ์žˆ์œผ๋ฏ€๋กœ Bash ๋ช…๋ น์„ ๊ตฌ๋ฌธ ๋ถ„์„ํ•  ์ˆ˜ ์—†์„ ๋•Œ ํŒจํ„ด์— ๊ด€๊ณ„์—†์ด hook์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ•„ํ„ฐ๋Š” ์ตœ์„ ์˜ ๋…ธ๋ ฅ์ด๋ฏ€๋กœ ํ•˜๋“œ ํ—ˆ์šฉ ๋˜๋Š” ๊ฑฐ๋ถ€๋ฅผ ์ ์šฉํ•˜๋ ค๋ฉด hook ๋Œ€์‹  ๊ถŒํ•œ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

if ํ•„๋“œ๋Š” ๊ถŒํ•œ ๊ทœ์น™๊ณผ ๋™์ผํ•œ ํŒจํ„ด์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค: "Bash(git *)", "Edit(*.ts)" ๋“ฑ. ์—ฌ๋Ÿฌ ๋„๊ตฌ ์ด๋ฆ„์„ ์ผ์น˜์‹œํ‚ค๋ ค๋ฉด ๊ฐ๊ฐ ์ž์‹ ์˜ if ๊ฐ’์„ ๊ฐ€์ง„ ๋ณ„๋„์˜ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ํŒŒ์ดํ”„ ๊ต๋Œ€๊ฐ€ ์ง€์›๋˜๋Š” matcher ์ˆ˜์ค€์—์„œ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.

if๋Š” ๋„๊ตฌ ์ด๋ฒคํŠธ์—์„œ๋งŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest ๋ฐ PermissionDenied. ๋‹ค๋ฅธ ์ด๋ฒคํŠธ์— ์ถ”๊ฐ€ํ•˜๋ฉด hook์ด ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Hook ์œ„์น˜ ๊ตฌ์„ฑ

Hook์„ ์ถ”๊ฐ€ํ•˜๋Š” ์œ„์น˜๋Š” ๋ฒ”์œ„๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค:

์œ„์น˜ ๋ฒ”์œ„ ๊ณต์œ  ๊ฐ€๋Šฅ
~/.claude/settings.json ๋ชจ๋“  ํ”„๋กœ์ ํŠธ ์•„๋‹ˆ์˜ค, ์ปดํ“จํ„ฐ์— ๋กœ์ปฌ
.claude/settings.json ๋‹จ์ผ ํ”„๋กœ์ ํŠธ ์˜ˆ, ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ์ปค๋ฐ‹ ๊ฐ€๋Šฅ
.claude/settings.local.json ๋‹จ์ผ ํ”„๋กœ์ ํŠธ ์•„๋‹ˆ์˜ค, gitignored
๊ด€๋ฆฌํ˜• ์ •์ฑ… ์„ค์ • ์กฐ์ง ์ „์ฒด ์˜ˆ, ๊ด€๋ฆฌ์ž ์ œ์–ด
Plugin hooks/hooks.json ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ํ™œ์„ฑํ™”๋˜์—ˆ์„ ๋•Œ ์˜ˆ, ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ ํ•จ๊ป˜ ๋ฒˆ๋“ค๋จ
Skill ๋˜๋Š” agent frontmatter Skill ๋˜๋Š” ์—์ด์ „ํŠธ๊ฐ€ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๋Š” ๋™์•ˆ ์˜ˆ, ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ์— ์ •์˜๋จ

Claude Code์—์„œ /hooks๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ด๋ฒคํŠธ๋ณ„๋กœ ๊ทธ๋ฃนํ™”๋œ ๋ชจ๋“  ๊ตฌ์„ฑ๋œ hooks๋ฅผ ์ฐพ์•„๋ด…๋‹ˆ๋‹ค.

๋ชจ๋“  hooks๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด ์„ค์ • ํŒŒ์ผ์—์„œ "disableAllHooks": true๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ด€๋ฆฌํ˜• ์„ค์ •์—์„œ ๊ตฌ์„ฑ๋œ Hooks๋Š” disableAllHooks๋„ ์„ค์ •๋˜์ง€ ์•Š๋Š” ํ•œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

Claude Code๊ฐ€ ์‹คํ–‰ ์ค‘์ธ ๋™์•ˆ ์„ค์ • ํŒŒ์ผ์„ ์ง์ ‘ ํŽธ์ง‘ํ•˜๋ฉด ํŒŒ์ผ ๊ฐ์‹œ์ž๊ฐ€ ์ผ๋ฐ˜์ ์œผ๋กœ hook ๋ณ€๊ฒฝ์„ ์ž๋™์œผ๋กœ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

ํ”„๋กฌํ”„ํŠธ ๊ธฐ๋ฐ˜ hooks

๊ฒฐ์ •๋ก ์  ๊ทœ์น™์ด ์•„๋‹Œ ํŒ๋‹จ์ด ํ•„์š”ํ•œ ๊ฒฐ์ •์˜ ๊ฒฝ์šฐ type: "prompt" hooks๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์…ธ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋Š” ๋Œ€์‹  Claude Code๋Š” ํ”„๋กฌํ”„ํŠธ์™€ hook์˜ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ Claude ๋ชจ๋ธ(๊ธฐ๋ณธ์ ์œผ๋กœ Haiku)์— ์ „์†กํ•˜์—ฌ ๊ฒฐ์ •์„ ๋‚ด๋ฆฝ๋‹ˆ๋‹ค. ๋” ๋งŽ์€ ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ model ํ•„๋“œ๋กœ ๋‹ค๋ฅธ ๋ชจ๋ธ์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋ธ์˜ ์œ ์ผํ•œ ์ž‘์—…์€ yes/no ๊ฒฐ์ •์„ JSON์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค:

  • "ok": true: ์ž‘์—…์ด ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค
  • "ok": false: ์ž‘์—…์ด ์ฐจ๋‹จ๋ฉ๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ์— ๋”ฐ๋ผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค:
    • Stop ๋ฐ SubagentStop: reason์ด Claude์—๊ฒŒ ํ”ผ๋“œ๋ฐฑ์œผ๋กœ ์ „๋‹ฌ๋˜์–ด ๊ณ„์† ์ž‘์—…ํ•ฉ๋‹ˆ๋‹ค
    • PreToolUse: ๋„๊ตฌ ํ˜ธ์ถœ์ด ๊ฑฐ๋ถ€๋˜๊ณ  reason์ด ๋„๊ตฌ ์˜ค๋ฅ˜๋กœ Claude์—๊ฒŒ ๋ฐ˜ํ™˜๋˜์–ด ์กฐ์ •ํ•˜๊ณ  ๊ณ„์†ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
    • PostToolUse, PostToolBatch, UserPromptSubmit, ๋ฐ UserPromptExpansion: ํ„ด์ด ์ข…๋ฃŒ๋˜๊ณ  reason์ด ๊ฒฝ๊ณ  ์ค„๋กœ ์ฑ„ํŒ…์— ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค

์ด ์˜ˆ์ œ๋Š” Stop hook์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  ์š”์ฒญ๋œ ์ž‘์—…์ด ์™„๋ฃŒ๋˜์—ˆ๋Š”์ง€ ๋ชจ๋ธ์— ๋ฌป์Šต๋‹ˆ๋‹ค. ๋ชจ๋ธ์ด "ok": false๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด Claude๋Š” ๊ณ„์† ์ž‘์—…ํ•˜๊ณ  reason์„ ๋‹ค์Œ ์ง€์นจ์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
          }
        ]
      }
    ]
  }
}

์ „์ฒด ๊ตฌ์„ฑ ์˜ต์…˜์€ ์ฐธ์กฐ์˜ ํ”„๋กฌํ”„ํŠธ ๊ธฐ๋ฐ˜ hooks๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

์—์ด์ „ํŠธ ๊ธฐ๋ฐ˜ hooks

๊ฒ€์ฆ์— ํŒŒ์ผ ๊ฒ€์‚ฌ ๋˜๋Š” ๋ช…๋ น ์‹คํ–‰์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ type: "agent" hooks๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ผ LLM ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ”„๋กฌํ”„ํŠธ hooks์™€ ๋‹ฌ๋ฆฌ ์—์ด์ „ํŠธ hooks๋Š” ํŒŒ์ผ์„ ์ฝ๊ณ  ์ฝ”๋“œ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ฉฐ ๊ฒฐ์ •์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ์ „์— ๋‹ค๋ฅธ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” subagent๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

์—์ด์ „ํŠธ hooks๋Š” ํ”„๋กฌํ”„ํŠธ hooks์™€ ๋™์ผํ•œ "ok" / "reason" ์‘๋‹ต ํ˜•์‹์„ ์‚ฌ์šฉํ•˜์ง€๋งŒ ๊ธฐ๋ณธ ํƒ€์ž„์•„์›ƒ์ด 60์ดˆ์ด๊ณ  ์ตœ๋Œ€ 50๊ฐœ์˜ ๋„๊ตฌ ์‚ฌ์šฉ ํ„ด์ž…๋‹ˆ๋‹ค.

์ด ์˜ˆ์ œ๋Š” Claude๊ฐ€ ์ค‘์ง€๋˜๊ธฐ ์ „์— ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

Hook ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋งŒ์œผ๋กœ ๊ฒฐ์ •์„ ๋‚ด๋ฆด ์ˆ˜ ์žˆ์„ ๋•Œ ํ”„๋กฌํ”„ํŠธ hooks๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฒ ์ด์Šค์˜ ์‹ค์ œ ์ƒํƒœ์— ๋Œ€ํ•ด ๋ฌด์–ธ๊ฐ€๋ฅผ ํ™•์ธํ•ด์•ผ ํ•  ๋•Œ ์—์ด์ „ํŠธ hooks๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ „์ฒด ๊ตฌ์„ฑ ์˜ต์…˜์€ ์ฐธ์กฐ์˜ ์—์ด์ „ํŠธ ๊ธฐ๋ฐ˜ hooks๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

HTTP hooks

type: "http" hooks๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์…ธ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋Š” ๋Œ€์‹  ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ HTTP ์—”๋“œํฌ์ธํŠธ์— POSTํ•ฉ๋‹ˆ๋‹ค. ์—”๋“œํฌ์ธํŠธ๋Š” ๋ช…๋ น hook์ด stdin์—์„œ ๋ฐ›์„ ๊ฒƒ๊ณผ ๋™์ผํ•œ JSON์„ ๋ฐ›๊ณ  ๋™์ผํ•œ JSON ํ˜•์‹์„ ์‚ฌ์šฉํ•˜์—ฌ HTTP ์‘๋‹ต ๋ณธ๋ฌธ์„ ํ†ตํ•ด ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

HTTP hooks๋Š” ์›น ์„œ๋ฒ„, ํด๋ผ์šฐ๋“œ ํ•จ์ˆ˜ ๋˜๋Š” ์™ธ๋ถ€ ์„œ๋น„์Šค๊ฐ€ hook ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ๋ฅผ ์›ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค: ์˜ˆ๋ฅผ ๋“ค์–ด ํŒ€ ์ „์ฒด์—์„œ ๋„๊ตฌ ์‚ฌ์šฉ ์ด๋ฒคํŠธ๋ฅผ ๊ธฐ๋กํ•˜๋Š” ๊ณต์œ  ๊ฐ์‚ฌ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.

์ด ์˜ˆ์ œ๋Š” ๋ชจ๋“  ๋„๊ตฌ ์‚ฌ์šฉ์„ ๋กœ์ปฌ ๋กœ๊น… ์„œ๋น„์Šค์— ๊ฒŒ์‹œํ•ฉ๋‹ˆ๋‹ค:

{
  "hooks": {
    "PostToolUse": [
      {
        "hooks": [
          {
            "type": "http",
            "url": "http://localhost:8080/hooks/tool-use",
            "headers": {
              "Authorization": "Bearer $MY_TOKEN"
            },
            "allowedEnvVars": ["MY_TOKEN"]
          }
        ]
      }
    ]
  }
}

์—”๋“œํฌ์ธํŠธ๋Š” ๋ช…๋ น hooks์™€ ๋™์ผํ•œ ์ถœ๋ ฅ ํ˜•์‹์„ ์‚ฌ์šฉํ•˜์—ฌ JSON ์‘๋‹ต ๋ณธ๋ฌธ์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋„๊ตฌ ํ˜ธ์ถœ์„ ์ฐจ๋‹จํ•˜๋ ค๋ฉด ์ ์ ˆํ•œ hookSpecificOutput ํ•„๋“œ์™€ ํ•จ๊ป˜ 2xx ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. HTTP ์ƒํƒœ ์ฝ”๋“œ๋งŒ์œผ๋กœ๋Š” ์ž‘์—…์„ ์ฐจ๋‹จํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

ํ—ค๋” ๊ฐ’์€ $VAR_NAME ๋˜๋Š” ${VAR_NAME} ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋ณด๊ฐ„์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. allowedEnvVars ๋ฐฐ์—ด์— ๋‚˜์—ด๋œ ๋ณ€์ˆ˜๋งŒ ํ•ด๊ฒฐ๋ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋ชจ๋“  $VAR ์ฐธ์กฐ๋Š” ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ „์ฒด ๊ตฌ์„ฑ ์˜ต์…˜ ๋ฐ ์‘๋‹ต ์ฒ˜๋ฆฌ๋Š” ์ฐธ์กฐ์˜ HTTP hooks๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

์ œํ•œ ์‚ฌํ•ญ ๋ฐ ๋ฌธ์ œ ํ•ด๊ฒฐ

์ œํ•œ ์‚ฌํ•ญ

Hooks๋ฅผ ์„ค๊ณ„ํ•  ๋•Œ ๋‹ค์Œ ์ œ์•ฝ ์‚ฌํ•ญ์„ ์—ผ๋‘์— ๋‘์‹ญ์‹œ์˜ค:

  • ๋ช…๋ น hooks๋Š” stdout, stderr ๋ฐ ์ข…๋ฃŒ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด์„œ๋งŒ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค. / ๋ช…๋ น์ด๋‚˜ ๋„๊ตฌ ํ˜ธ์ถœ์„ ์ง์ ‘ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. additionalContext๋ฅผ ํ†ตํ•ด ๋ฐ˜ํ™˜๋œ ํ…์ŠคํŠธ๋Š” Claude๊ฐ€ ์ผ๋ฐ˜ ํ…์ŠคํŠธ๋กœ ์ฝ๋Š” ์‹œ์Šคํ…œ ์•Œ๋ฆผ์œผ๋กœ ์ฃผ์ž…๋ฉ๋‹ˆ๋‹ค. HTTP hooks๋Š” ์‘๋‹ต ๋ณธ๋ฌธ์„ ํ†ตํ•ด ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค.
  • Hook ํƒ€์ž„์•„์›ƒ์€ ์œ ํ˜•์— ๋”ฐ๋ผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. timeout ํ•„๋“œ(์ดˆ ๋‹จ์œ„)๋กœ hook๋‹น ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • command, http, mcp_tool: 10๋ถ„. UserPromptSubmit์€ ์ด๋ฅผ 30์ดˆ๋กœ ๋‚ฎ์ถ”๊ณ , MessageDisplay๋Š” ์ด๋ฅผ 10์ดˆ๋กœ ๋‚ฎ์ถฅ๋‹ˆ๋‹ค.
    • prompt: 30์ดˆ.
    • agent: 60์ดˆ.
  • PostToolUse hooks๋Š” ๋„๊ตฌ๊ฐ€ ์ด๋ฏธ ์‹คํ–‰๋˜์—ˆ์œผ๋ฏ€๋กœ ์ž‘์—…์„ ์ทจ์†Œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
  • PermissionRequest hooks๋Š” ๋น„๋Œ€ํ™”ํ˜• ๋ชจ๋“œ์—์„œ -p ํ”Œ๋ž˜๊ทธ์™€ ํ•จ๊ป˜ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ž๋™ํ™”๋œ ๊ถŒํ•œ ๊ฒฐ์ •์„ ์œ„ํ•ด PreToolUse hooks๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • Stop hooks๋Š” ์ž‘์—… ์™„๋ฃŒ ์‹œ์—๋งŒ์ด ์•„๋‹ˆ๋ผ Claude๊ฐ€ ์‘๋‹ต์„ ์™„๋ฃŒํ•  ๋•Œ๋งˆ๋‹ค ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์ค‘๋‹จ ์‹œ์—๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. API ์˜ค๋ฅ˜๋Š” ๋Œ€์‹  StopFailure๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
  • ์—ฌ๋Ÿฌ PreToolUse hooks๊ฐ€ updatedInput์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ ๋„๊ตฌ์˜ ์ธ์ˆ˜๋ฅผ ๋‹ค์‹œ ์“ธ ๋•Œ ๋งˆ์ง€๋ง‰์œผ๋กœ ์™„๋ฃŒ๋œ ๊ฒƒ์ด ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค. Hooks๋Š” ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰๋˜๋ฏ€๋กœ ์ˆœ์„œ๋Š” ๋น„๊ฒฐ์ •์ ์ž…๋‹ˆ๋‹ค. ๋™์ผํ•œ ๋„๊ตฌ์˜ ์ž…๋ ฅ์„ ์ˆ˜์ •ํ•˜๋Š” hook์ด ๋‘ ๊ฐœ ์ด์ƒ ์žˆ๋Š” ๊ฒƒ์„ ํ”ผํ•ฉ๋‹ˆ๋‹ค.

Hooks ๋ฐ ๊ถŒํ•œ ๋ชจ๋“œ

PreToolUse hooks๋Š” ๋ชจ๋“  ๊ถŒํ•œ ๋ชจ๋“œ ํ™•์ธ ์ „์— ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. permissionDecision: "deny"๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” hook์€ bypassPermissions ๋ชจ๋“œ ๋˜๋Š” --dangerously-skip-permissions์—์„œ๋„ ๋„๊ตฌ๋ฅผ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ๊ถŒํ•œ ๋ชจ๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ ์šฐํšŒํ•  ์ˆ˜ ์—†๋Š” ์ •์ฑ…์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฐ˜๋Œ€๋Š” ์‚ฌ์‹ค์ด ์•„๋‹™๋‹ˆ๋‹ค: "allow"๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” hook์€ ์„ค์ •์˜ ๊ฑฐ๋ถ€ ๊ทœ์น™์„ ์šฐํšŒํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์กฐ์ง์ด ask๋กœ ์„ค์ •ํ•œ ์ปค๋„ฅํ„ฐ ๋„๊ตฌ ๋˜๋Š” requiresUserInteraction์œผ๋กœ ํ‘œ์‹œ๋œ MCP ๋„๊ตฌ์˜ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์–ต์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. Hooks๋Š” ์ œํ•œ์„ ๊ฐ•ํ™”ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๊ถŒํ•œ ๊ทœ์น™์ด ํ—ˆ์šฉํ•˜๋Š” ๊ฒƒ์„ ์ดˆ๊ณผํ•˜์—ฌ ์™„ํ™”ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

Hook์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ

Hook์ด ๊ตฌ์„ฑ๋˜์—ˆ์ง€๋งŒ ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • /hooks๋ฅผ ์‹คํ–‰ํ•˜๊ณ  hook์ด ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฒคํŠธ ์•„๋ž˜์— ๋‚˜ํƒ€๋‚˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค
  • Matcher ํŒจํ„ด์ด ๋„๊ตฌ ์ด๋ฆ„๊ณผ ์ •ํ™•ํžˆ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. Matchers๋Š” ๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ถ„์ž…๋‹ˆ๋‹ค
  • ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฒคํŠธ ์œ ํ˜•์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค: PreToolUse๋Š” ๋„๊ตฌ ์‹คํ–‰ ์ „์— ๋ฐœ์ƒํ•˜๊ณ  PostToolUse๋Š” ํ›„์— ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค
  • ๋น„๋Œ€ํ™”ํ˜• ๋ชจ๋“œ์—์„œ -p ํ”Œ๋ž˜๊ทธ์™€ ํ•จ๊ป˜ PermissionRequest hooks๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋Œ€์‹  PreToolUse๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค

์ถœ๋ ฅ์— Hook ์˜ค๋ฅ˜

ํŠธ๋žœ์Šคํฌ๋ฆฝํŠธ์— "PreToolUse hook error: ..." ๊ฐ™์€ ๋ฉ”์‹œ์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

  • ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์˜ˆ๊ธฐ์น˜ ์•Š๊ฒŒ 0์ด ์•„๋‹Œ ์ฝ”๋“œ๋กœ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ƒ˜ํ”Œ JSON์„ ํŒŒ์ดํ”„ํ•˜์—ฌ ์ˆ˜๋™์œผ๋กœ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค:
    echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh
    echo $?  # ์ข…๋ฃŒ ์ฝ”๋“œ ํ™•์ธ
    
  • "command not found"๊ฐ€ ํ‘œ์‹œ๋˜๋ฉด ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ${CLAUDE_PROJECT_DIR}์„ ์‚ฌ์šฉํ•˜์—ฌ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. ์…ธ ์ธ์šฉ์„ ์™„์ „ํžˆ ํ”ผํ•˜๋ ค๋ฉด "args": []๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ exec form์œผ๋กœ ์ „ํ™˜ํ•˜๋ฉด ์…ธ ์—†์ด ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
  • "jq: command not found"๊ฐ€ ํ‘œ์‹œ๋˜๋ฉด jq๋ฅผ ์„ค์น˜ํ•˜๊ฑฐ๋‚˜ JSON ๊ตฌ๋ฌธ ๋ถ„์„์„ ์œ„ํ•ด Python/Node.js๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค
  • ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š์œผ๋ฉด ์‹คํ–‰ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค: chmod +x ./my-hook.sh

`/hooks`์— ๊ตฌ์„ฑ๋œ hooks๊ฐ€ ์—†์Œ

์„ค์ • ํŒŒ์ผ์„ ํŽธ์ง‘ํ–ˆ์ง€๋งŒ hooks๊ฐ€ ๋ฉ”๋‰ด์— ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • ํŒŒ์ผ ํŽธ์ง‘์€ ์ผ๋ฐ˜์ ์œผ๋กœ ์ž๋™์œผ๋กœ ์„ ํƒ๋ฉ๋‹ˆ๋‹ค. ๋ช‡ ์ดˆ ํ›„์— ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์œผ๋ฉด ํŒŒ์ผ ๊ฐ์‹œ์ž๊ฐ€ ๋ณ€๊ฒฝ์„ ๋†“์ณค์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ์„ธ์…˜์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜์—ฌ ๊ฐ•์ œ๋กœ ๋‹ค์‹œ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.
  • JSON์ด ์œ ํšจํ•œ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค: ํ›„ํ–‰ ์‰ผํ‘œ ๋ฐ ์ฃผ์„์€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค
  • ์„ค์ • ํŒŒ์ผ์ด ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค: ํ”„๋กœ์ ํŠธ hooks์˜ ๊ฒฝ์šฐ .claude/settings.json, ์ „์—ญ hooks์˜ ๊ฒฝ์šฐ ~/.claude/settings.json

Stop hook์ด ๋ธ”๋ก ์ƒํ•œ์— ๋„๋‹ฌํ•จ

Claude๊ฐ€ ๊ณ„์† ์ž‘์—…ํ•˜๋Š” ๋Œ€์‹  ์ค‘์ง€ํ•˜๊ณ  Stop hook์ด ๋„ˆ๋ฌด ๋งŽ์€ ํšŸ์ˆ˜๋ฅผ ์—ฐ์†์œผ๋กœ ์ฐจ๋‹จํ–ˆ๋‹ค๋Š” ๊ฒฝ๊ณ ๋กœ ํ„ด์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.

Claude Code๋Š” Stop hook์ด ์ง„ํ–‰ ์—†์ด 8๋ฒˆ ์—ฐ์†์œผ๋กœ ์ฐจ๋‹จํ•œ ํ›„ ์žฌ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. Hook ์Šคํฌ๋ฆฝํŠธ๋Š” ์ด๋ฏธ ํŠธ๋ฆฌ๊ฑฐ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. JSON ์ž…๋ ฅ์—์„œ stop_hook_active ํ•„๋“œ๋ฅผ ๊ตฌ๋ฌธ ๋ถ„์„ํ•˜๊ณ  true์ธ ๊ฒฝ์šฐ ์กฐ๊ธฐ์— ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค:

#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0  # Claude๊ฐ€ ์ค‘์ง€๋˜๋„๋ก ํ—ˆ์šฉ
fi
# ... hook ๋กœ์ง์˜ ๋‚˜๋จธ์ง€

Hook์ด ์ˆ˜๋ ดํ•˜๊ธฐ ์œ„ํ•ด 8๋ฒˆ ์ด์ƒ์˜ ๋ฐ˜๋ณต์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ CLAUDE_CODE_STOP_HOOK_BLOCK_CAP์œผ๋กœ ์ƒํ•œ์„ ์˜ฌ๋ฆฝ๋‹ˆ๋‹ค.

JSON ๊ฒ€์ฆ ์‹คํŒจ

Hook ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์œ ํšจํ•œ JSON์„ ์ถœ๋ ฅํ•˜๋”๋ผ๋„ Claude Code์— JSON ๊ตฌ๋ฌธ ๋ถ„์„ ์˜ค๋ฅ˜๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

Claude Code๊ฐ€ ์…ธ ํ˜•์‹ ๋ช…๋ น hook(args ์—†๋Š” hook)์„ ์‹คํ–‰ํ•  ๋•Œ macOS ๋ฐ Linux์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ sh -c๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ Windows์—์„œ๋Š” Git Bash๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ์…ธ์€ ๋น„๋Œ€ํ™”ํ˜•์ด์ง€๋งŒ Git Bash ๋ฐ ์ผ๋ถ€ ๊ตฌ์„ฑ(์˜ˆ: BASH_ENV๊ฐ€ ~/.bashrc๋ฅผ ๊ฐ€๋ฆฌํ‚ด)์€ ์—ฌ์ „ํžˆ ํ”„๋กœํ•„์„ ์†Œ์‹ฑํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ํ”„๋กœํ•„์— ๋ฌด์กฐ๊ฑด์ ์ธ echo ๋ฌธ์ด ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉด ์ถœ๋ ฅ์ด hook์˜ JSON์— ์•ž์— ๋ถ™์Šต๋‹ˆ๋‹ค:

Shell ready on arm64
{"decision": "block", "reason": "Not allowed"}

Claude Code๋Š” ์ด๋ฅผ JSON์œผ๋กœ ๊ตฌ๋ฌธ ๋ถ„์„ํ•˜๋ ค๊ณ  ํ•˜๊ณ  ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์ˆ˜์ •ํ•˜๋ ค๋ฉด ์…ธ ํ”„๋กœํ•„์˜ echo ๋ฌธ์„ ๋ž˜ํ•‘ํ•˜์—ฌ ๋Œ€ํ™”ํ˜• ์…ธ์—์„œ๋งŒ ์‹คํ–‰๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค:

# ~/.zshrc ๋˜๋Š” ~/.bashrc์—์„œ
if [[ $- == *i* ]]; then
  echo "Shell ready"
fi

$- ๋ณ€์ˆ˜๋Š” ์…ธ ํ”Œ๋ž˜๊ทธ๋ฅผ ํฌํ•จํ•˜๊ณ  i๋Š” ๋Œ€ํ™”ํ˜•์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. Hooks๋Š” ๋น„๋Œ€ํ™”ํ˜• ์…ธ์—์„œ ์‹คํ–‰๋˜๋ฏ€๋กœ echo๋Š” ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.

๋””๋ฒ„๊ทธ ๊ธฐ๋ฒ•

ํŠธ๋žœ์Šคํฌ๋ฆฝํŠธ ๋ณด๊ธฐ๋Š” Ctrl+O๋กœ ์ „ํ™˜๋˜๋ฉฐ ๋ฐœ์ƒํ•œ ๊ฐ hook์— ๋Œ€ํ•ด ํ•œ ์ค„ ์š”์•ฝ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค: ์„ฑ๊ณต์€ ์ž๋™์œผ๋กœ ํ‘œ์‹œ๋˜๊ณ , ์ฐจ๋‹จ ์˜ค๋ฅ˜๋Š” stderr๋ฅผ ํ‘œ์‹œํ•˜๋ฉฐ, ์ฐจ๋‹จํ•˜์ง€ ์•Š๋Š” ์˜ค๋ฅ˜๋Š” <hook name> hook error ๊ณต์ง€๋ฅผ ํ‘œ์‹œํ•œ ํ›„ stderr์˜ ์ฒซ ๋ฒˆ์งธ ์ค„์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

์ „์ฒด ์‹คํ–‰ ์„ธ๋ถ€ ์ •๋ณด(์ผ์น˜ํ•œ hooks, ์ข…๋ฃŒ ์ฝ”๋“œ, stdout ๋ฐ stderr ํฌํ•จ)๋Š” ๋””๋ฒ„๊ทธ ๋กœ๊ทธ๋ฅผ ์ฝ์Šต๋‹ˆ๋‹ค. claude --debug-file /tmp/claude.log๋กœ Claude Code๋ฅผ ์‹œ์ž‘ํ•˜์—ฌ ์•Œ๋ ค์ง„ ๊ฒฝ๋กœ์— ์“ฐ๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ํ„ฐ๋ฏธ๋„์—์„œ tail -f /tmp/claude.log๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ํ”Œ๋ž˜๊ทธ ์—†์ด ์‹œ์ž‘ํ•œ ๊ฒฝ์šฐ ์„ธ์…˜ ์ค‘์— /debug๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๋กœ๊น…์„ ํ™œ์„ฑํ™”ํ•˜๊ณ  ๋กœ๊ทธ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.

์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ