你有沒有遇過這種情況:PostToolUse hook 用 shell script 跑 linter,邏輯一複雜,腳本就開始長成怪物。條件判斷七八層,jq 拼成一行天書,維護到後來你自己都看不懂。

或者另一種情況:你想讓 hook 判斷一件事,但那件事需要「看懂」才能決定——不是規則,是判斷。rm -rf / 要攔,但 rm -rf ./node_modules 要放行,這個差別不是 regex 能搞定的。

Claude Code Hooks 現在有三種新的 hook 類型,每一種對應一類問題。這篇文章不重複 Hooks 完整指南 裡的基礎,直接從新東西講起。


三種類型,對應三個問題

先把格局建出來。Claude Code 的 hook 現在有五種 type

  • command:執行本機 shell 腳本(舊的,大家都在用)
  • http:POST 事件資料到一個 URL
  • prompt:單次 LLM 評估,回傳 yes/no
  • agent:可以用工具的 multi-turn 子代理,跑完再回傳 yes/no
  • mcp_tool:呼叫已連接的 MCP server 上的工具

今天重點放在 httppromptagent 三個。


type: "http" — 把 Hook 邏輯搬到服務端

類比: 門禁系統不用每一扇門都裝一台電腦,有一個中央控制台,門觸發事件,控制台決定開不開。

http hook 的概念完全一樣。它不在本機跑腳本,而是把事件資料 POST 到一個 HTTP 端點,由那個服務決定要不要放行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/tool-use",
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}

收到的 request body 跟 command hook 的 stdin 格式一模一樣。服務端用同樣的 JSON 格式回傳決策,想 block 就回 hookSpecificOutput 裡面帶 deny decision。

這種設計最適合的場景:團隊共用稽核服務。你在 CI 跑一個 validation server,每個工程師的 Claude Code 都把工具呼叫事件打過去,server 統一記錄、統一執行 policy,不用在每個人的 .claude/settings.json 維護同一份 script。有新的 policy 要加?改 server 就好,不用推設定到每台電腦。

一個細節值得注意:header 裡面的 $VAR_NAME 會被展開,但只有 allowedEnvVars 列出來的變數才會被解析,其他一律留空。這是安全設計,防止 hook 腳本偷偷把環境變數洩出去。


type: "prompt" — 讓 LLM 做判斷的 Hook

類比: 請一個有判斷力的同事幫你把關,不是訓練一個規則機器人。

command hook 只能跑確定性的規則。但有時候你需要的不是規則,是語意判斷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
}
]
}
]
}
}

prompt hook 把 hook 的輸入資料 + 你的 prompt 一起送給 Claude(預設用 Haiku,也可以指定 model)。LLM 看完回傳 {"ok": true}{"ok": false, "reason": "..."} 這個格式。

如果是 ok: false,Claude Code 會把 reason 餵回給 Claude,讓它繼續工作——所以 Stop hook 搭 prompt 的組合,可以讓 Claude 在真正完成任務前不停下來。

一個實際用法:某個任務要求「必須更新 README」。你不想每次手動確認,但 regex 也抓不到「有沒有更新到對的地方」。prompt hook 讓模型自己判斷,比維護一堆 grep pattern 合理得多。

有一個 anti-loop 機制要注意。如果你的 Stop hook 觸發後讓 Claude 繼續工作,Claude 完成後又再觸發 Stop hook,有機會無限循環。解法是在 hook 腳本裡讀 stop_hook_active 這個欄位:

1
2
3
4
5
6
#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # 已經在 hook 觸發的回合中,直接放行
fi
# ... 正常判斷邏輯

stop_hook_activetrue 代表這次 Stop 是 hook 把 Claude 叫回來再跑一輪後的結果,直接放行就好。


type: "agent" — 有工具的 Hook

類比: 不只是「請 LLM 評估一段文字」,而是「派一個能自己去翻資料夾、跑指令的代理人來驗收」。

prompt hook 只能看 hook 輸入資料本身。但如果你要驗的東西在別的地方——比如「test 有沒有過」——LLM 光看 hook input 是不夠的,它需要去實際跑 test。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Verify that all unit tests pass. Run the test suite and check the results.",
"timeout": 120
}
]
}
]
}
}

agent hook 會 spawn 一個子代理,這個子代理可以用工具(讀檔、跑 bash)、跑多輪、最後回傳 {"ok": true/false, "reason": "..."} 的格式。

預設 timeout 是 60 秒,最多 50 個 tool-use turns。如果你要跑完整的測試套件,記得把 timeout 拉到夠大。

官方文件有一句話很直接:「Use prompt hooks when the hook input data alone is enough to make a decision. Use agent hooks when you need to verify something against the actual state of the codebase.」判準就這麼簡單:資料在不在 hook input 裡面。

因為 agent hook 是實驗性功能(官方有標 experimental),生產工作流建議先用 command hook,需要跨工具驗收的場景才上 agent hook。


新事件:CwdChangedFileChanged

除了新 hook 類型,還多了幾個過去沒有的事件,值得知道。

CwdChanged 在 Claude Code 的 Bash tool 換目錄時觸發。FileChanged 在特定檔案被外部程序改動時觸發。這兩個最常見的用法是跟 direnv 搭配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"hooks": {
"CwdChanged": [
{
"hooks": [
{
"type": "command",
"command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
}
]
}
]
}
}

CLAUDE_ENV_FILE 是 Claude Code 設計的一個機制——你寫進去的 bash 環境變數,Claude Code 在跑下一個 Bash 命令前會先 source 進來。所以這個 hook 的效果是:Claude Code 每次換目錄,direnv 的環境就跟著自動切換,不用你手動設定。

這對多 project、每個 project 有不同 .envrc 的工程師來說,是個踩坑預防工具。Claude 幫你改了 staging 的設定,但環境變數還是指向 production?裝這個 hook 就不會有這種問題。


if 欄位:精確過濾到工具呼叫的引數層

matcher 過濾的是工具名稱。if 過濾的是工具呼叫的引數內容,用的是跟 permissions 一樣的語法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git *)",
"command": ".claude/hooks/check-git-policy.sh"
}
]
}
]
}
}

這個設定的意思是:只有在 Bash 命令是 git * 開頭時才觸發 hook,純粹的 npm installls 不觸發。這樣可以大幅減少不必要的 hook 進程,降低整體延遲。

if 只對工具事件有效(PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest、PermissionDenied),加在其他事件上會讓 hook 完全不跑。


跟其他工具比

Cursor 的 .cursorrules、Copilot 的 .github/copilot-instructions.md 都是靜態指令——你寫什麼,AI 就照著走,沒有觸發點、沒有決策回饋。

Claude Code Hooks 的設計哲學不一樣:它是事件驅動的確定性控制層。你不是在說「你應該這樣做」,你是在說「每次這個事件發生,這個腳本一定跑」。跟 AI 的「記不記得」無關。

再加上現在的 promptagent hook,你可以在確定性行為裡面嵌入判斷性行為——shell script 做的事用 shell script,需要語意判斷的事交給 LLM,需要驗收 codebase 的事派子代理。三層分工,不需要什麼都硬塞進一個腳本裡。

這個彈性是 Cursor Rules 和 Copilot Instructions 沒有的結構。不是說哪個比較好,是說它們解決的是不同的問題。


背後就兩條線:什麼時候用確定性、什麼時候用判斷性。Shell script 負責確定性的事,prompt/agent hook 負責需要判斷的事,http hook 把兩者都搬到可以集中管理的地方。搞清楚這條線,Hooks 就不難設計。

原文來源:Automate workflows with hooks - Claude Code Docs