你有沒有遇過這種狀況:叫 Claude Code 改完檔案,結果忘了跑 formatter,code review 被打回來的全是 style issue?

或者更痛的——Claude 很盡責地幫你改了五個檔案,你說「好了可以了」,它就真的停了。然後你才發現 test 沒跑、lint 沒過、有一個 .env 被改到。

Hooks 就是拿來解決這類問題的。它不是什麼 AI 魔法,反而是整套 Claude Code 裡面最不 AI 的部分——純粹的 shell 腳本,在特定時間點被觸發,做你設定好的事情。確定性的行為,不靠 LLM 判斷。


概念:Hook 是什麼?

一句話講完:在 Claude Code 生命週期的特定節點,自動執行你寫的腳本。

跟 Git hooks 很像。pre-commit hook 在 commit 前跑 lint,Claude Code 的 PreToolUse hook 在工具執行前跑你的檢查。概念相通,只是觸發的時機不一樣。

目前支援的 hook 事件超過 20 個,常用的幾個:

事件 觸發時機
PreToolUse 工具執行,可以攔截
PostToolUse 工具執行,適合做後處理
Notification Claude 等待你輸入時
Stop Claude 回應完畢時
SessionStart Session 開始或恢復時
ConfigChange 設定檔被修改時
PermissionRequest 權限對話框彈出時

每個 hook 都有一個 matcher 可以過濾條件。比方 PostToolUse 配上 matcher: "Edit|Write",就只在檔案被編輯時觸發,Bash 指令不會被影響。


實戰一:檔案一改完就自動 format

這大概是最多人需要的場景。Claude 改完 code,Prettier 立刻跑過去。不用每次提醒它「記得 format」。

在專案根目錄的 .claude/settings.json 加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}

PostToolUse 代表工具執行成功後觸發。matcher: "Edit|Write" 限制只在檔案編輯工具上生效。腳本從 stdin 拿到 JSON,用 jq 抽出檔案路徑,丟給 Prettier。

一氣呵成,Claude 完全不需要知道 Prettier 的存在。


實戰二:桌面通知 — 不用盯著 terminal

Claude 在跑長任務的時候,你切去看 Slack、看文件,回來才發現它早就問你問題了,空等了十分鐘。

Notification hook 解決這個痛點:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code 在等你\" with title \"Claude Code\"'"
}
]
}
]
}
}

macOS 用 osascript,Linux 用 notify-sendmatcher 留空表示所有通知類型都觸發。裝好之後就不用一直盯著 terminal 了。


實戰三:保護敏感檔案不被修改

.env 被 Claude 改到是真的會出事的那種。package-lock.json 被動到也很頭痛。

PreToolUse hook 加一層保護:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
# .claude/hooks/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

註冊方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}

重點是 exit codeexit 2 代表攔截,Claude 會收到 stderr 的訊息然後自動調整策略,不會硬幹。exit 0 代表放行。


實戰四:Compaction 後重新注入上下文

Claude Code 的 context window 滿了會觸發 compaction(壓縮),把對話摘要化。問題是壓縮之後某些重要指令可能被吃掉——比方說「這個專案用 Bun 不用 npm」。

SessionStart hook 配合 compact matcher 可以在壓縮後重新注入記憶:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"
}
]
}
]
}
}

stdout 的內容會直接注入 Claude 的 context。你也可以把 echo 換成 git log --oneline -5,讓 Claude 壓縮後自動知道最近改了什麼。


進階:三種 Hook 類型

除了最基本的 command 類型,Hooks 還有三種進階玩法:

Prompt-based Hook:叫一個輕量模型(預設 Haiku)做判斷。不跑腳本,直接問 LLM「這個動作 OK 嗎?」適合需要語義理解的場景。

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

Agent-based Hook:比 prompt hook 更強,會起一個 subagent 去實際跑測試、讀檔案。適合「先跑 test 確認全過才能停」的場景。

1
2
3
4
5
{
"type": "agent",
"prompt": "Verify that all unit tests pass. Run the test suite and check the results.",
"timeout": 120
}

HTTP Hook:把事件資料 POST 到一個 endpoint。適合團隊級的 audit logging 或串接外部服務。

三種類型的共通點:都用同一套 JSON 格式溝通,只是執行邏輯不同。


設定檔放哪裡?

位置 作用範圍 可以 commit 嗎
~/.claude/settings.json 全域,所有專案 不行
.claude/settings.json 單一專案 可以
.claude/settings.local.json 單一專案(gitignore) 不行
Plugin hooks/hooks.json Plugin 啟用時生效 跟著 Plugin 走

想看目前所有 hook 的狀態,在 Claude Code 裡打 /hooks 就會列出來。


除錯技巧

Hook 沒觸發?先檢查這幾件事:

  1. /hooks 選單裡有沒有看到你的 hook
  2. matcher 是不是 case-sensitive 的問題(bashBash
  3. 腳本有沒有執行權限(chmod +x
  4. jq 有沒有裝

想看更多執行細節,按 Ctrl+O 開 verbose mode,或用 claude --debug 啟動。所有 hook 的 stdout/stderr 都會印出來。

有個常見的坑:如果你的 .zshrc 裡有不帶條件的 echo,hook 跑的時候那個 echo 會混進 JSON 輸出,導致 parse 失敗。解法是把 echo 包在 interactive shell 判斷裡:

1
2
3
if [[ $- == *i* ]]; then
echo "Shell ready"
fi

小結

Hooks 讓 Claude Code 從「有時候記得做」變成「每次一定做」。format、lint、protect、notify——這些不需要 AI 判斷的事情,就不應該交給 AI 判斷。

腳本寫一次,之後每個 session 自動生效。這種確定性,在跟 AI 協作的時候格外珍貴。

原文來源:Automate workflows with hooks - Claude Code Docs
參考來源:Claude Code in Action - Anthropic Academy