用法
Claude Code Hooks 教程:怎麼配置 hooks、選事件、套用現成示例
Hooks 是使用者自定義的 shell 命令,Claude Code 在生命週期的特定時刻執行它們。執行的是 hook 而不是模型,所以這個動作一定會發生。這一頁是操作步驟:hook 放在哪裡、一條 hook 長什麼樣、哪些事件最常用、四個可以直接貼上的示例、退出碼怎麼攔截操作、hook 沒反應時看什麼。內容按 Anthropic 的 Automate actions with hooks 和 Hooks reference 中文整理,2026 年 10 月 30 日對照原文核對。
- hook 就是設定檔案裡的一個
hooks塊:先選事件,再加 matcher 過濾,最後定義要執行的 handler。放在~/.claude/settings.json對你所有專案生效,放在.claude/settings.json可以提交到倉庫和團隊共享。 - 在 Claude Code 裡輸入
/hooks,確認 hook 登記在正確的事件下面。 - 退出碼 0 表示成功;退出碼 2 在可以攔截的事件上阻止操作,你寫到 stderr 的內容就是原因;其他退出碼是非阻塞錯誤。
- command hook 以你的完整使用者權限執行 shell 命令,新增之前先審查並測試每一條命令。
Hooks 是什麼,什麼時候該用
Hooks 是使用者自定義的 shell 命令。Claude Code 在生命週期的特定時刻執行它們,讓你獲得確定性的控制:某些動作一定會發生,而不是依賴 LLM 自己決定要不要執行。可以用 hooks 來落實專案規則、自動化重複任務,以及把 Claude Code 接進你已有的工具。
Anthropic 給出的用法包括:編輯後格式化檔案、在命令執行前攔截命令、Claude 需要輸入時發通知、工作階段開始時注入上下文。需要判斷而不是確定性規則的決定,指南指向 prompt hook 和 agent hook,它們用 Claude 模型來評估條件。想在每次工作階段開始時注入上下文,指南建議改用 CLAUDE.md。想給 Claude 額外的指令和可執行命令,見技能(skills)。
安全提醒:command hook 以你的完整使用者權限執行 shell 命令,可以修改、刪除或訪問你的使用者賬號能訪問的任何檔案。新增到配置之前,先審查並測試所有 hook 命令。
Hooks 配置在哪裡
Hooks 定義在 JSON 設定檔案裡。hook 加在哪裡,決定了它的作用範圍:
| 位置 | 作用範圍 | 能否共享 |
|---|---|---|
~/.claude/settings.json | 你的所有專案 | 否,只在你的機器上 |
.claude/settings.json | 單個專案 | 是,可以提交到倉庫 |
.claude/settings.local.json | 單個專案 | 否,Claude Code 往裡儲存設定時會被 gitignore |
| 託管策略設定(Managed policy settings) | 整個組織 | 是,由管理員控制 |
外掛的 hooks/hooks.json | 外掛啟用時 | 是,隨外掛打包 |
| 技能(skill)的 frontmatter | 技能被呼叫之後的本次工作階段 | 是,定義在技能檔案裡 |
| 子代理(subagent)的 frontmatter | 該子代理執行期間 | 是,定義在子代理檔案裡 |
在 Claude Code 裡輸入 /hooks,會開啟一個只讀的 hooks 瀏覽器,列出已配置的 hooks,並標明每個 hook 的來源,例如使用者設定、專案設定、本地設定、某個外掛或當前工作階段。想停用 hooks,在設定檔案裡寫 "disableAllHooks": true。Claude Code 執行時直接編輯設定檔案,檔案監視器通常會自動讀取 hook 的改動。
在互動工作階段裡,Claude Code 會先扣住所有設定檔案裡的 hooks,包括你自己的 ~/.claude/settings.json,直到你接受該資料夾的工作區信任對話框,或者接受某個信任範圍覆蓋到它的上級目錄。
一條 hook 的結構
配置分三層巢狀:選一個要響應的 hook 事件,比如 PreToolUse 或 Stop;加一個 matcher 組來過濾觸發時機,比如“只對 Bash 工具”;再定義匹配時要執行的一個或多個 hook handler。每個事件名都是同一個 hooks 物件裡的鍵。下面是 Anthropic 指南里的第一個 hook,macOS 上的桌面通知:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
如果你的設定檔案裡已經有 hooks 鍵,把新事件作為已有事件鍵的同級項加進去,不要替換整個物件。
內層 hooks 陣列裡的每個物件是一個 hook handler。大多數 hook 用 "type": "command",執行一條 shell 命令。另外還有四種型別:"type": "http"、"type": "mcp_tool"、"type": "prompt" 和 "type": "agent"。Agent hook 是實驗性的,可能會變。handler 的常用欄位:
| 欄位 | 必填 | 作用 |
|---|---|---|
type | 是 | "command"、"http"、"mcp_tool"、"prompt" 或 "agent" |
if | 否 | 用權限規則語法過濾這個 hook 何時執行,例如 "Bash(git *)" 或 "Edit(*.ts)"。只在工具事件上求值:PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest 和 PermissionDenied。在其他事件上,設了 if 的 hook 永遠不會執行 |
timeout | 否 | 取消前等待的秒數。預設值:command、http、mcp_tool 為 600;prompt 為 30;agent 為 60 |
statusMessage | 否 | hook 執行期間顯示的自定義轉圈提示 |
Matcher 怎麼寫
沒有 matcher 時,hook 在對應事件每次發生時都會觸發。matcher 欄位用來過濾觸發時機,怎麼求值取決於它包含哪些字元:
| Matcher 的值 | 求值方式 | 示例 |
|---|---|---|
"*"、"" 或省略 | 匹配全部 | 該事件每次發生都觸發 |
只含字母、數字、_、-、空格、, 和 | | 精確字串,或用 |、, 分隔的精確字串列表,分隔符兩側可以有空白 | Bash 只匹配 Bash 工具;Edit|Write 和 Edit, Write 都精確匹配這兩個工具中的任意一個 |
| 含有其他任何字元 | JavaScript 正規表示式,不錨定 | ^Notebook 匹配名字以 Notebook 開頭的任何工具;mcp__memory__.* 匹配 memory 伺服器的所有工具 |
每種事件匹配的欄位不同:PreToolUse 和 PostToolUse 匹配工具名,SessionStart 匹配工作階段的啟動方式(startup、resume、clear、compact、fork),Notification 匹配通知型別。Matcher 區分大小寫。UserPromptSubmit、Stop 等少數事件不支援 matcher,每次發生都會觸發。
if 欄位比 matcher 更細,matcher 只在組的層面按工具名過濾。下面這個配置只在 Claude 用 git 命令時執行 hook,而不是所有 Bash 命令:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git *)",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh"
}
]
}
]
}
}
這個過濾是盡力而為的,所以指南說,要強制執行硬性的允許或拒絕,請用權限系統,不要用 hook。
最常用的事件
事件觸發時,Claude Code 會並行執行匹配的 hooks。指南列出的事件遠不止這些;下表是本頁示例用到的,加上幾個常見的。
| 事件 | 何時觸發 | 能否攔截 |
|---|---|---|
SessionStart | 工作階段開始或恢復時 | 不能。退出碼 2 只把 stderr 顯示給使用者 |
UserPromptSubmit | 提交提示詞時,在 Claude 處理之前 | 能,攔截這條提示詞 |
PreToolUse | 工具呼叫執行之前 | 能,攔截這次工具呼叫 |
PermissionRequest | 工具呼叫需要權限決定時 | 不能。不認退出碼 2;要拒絕,改用 decision 物件 |
PostToolUse | 工具呼叫成功之後 | 不能。把 stderr 顯示給 Claude;工具已經執行過了 |
Notification | Claude Code 發出通知時 | 不能。退出碼和 stderr 都被忽略 |
Stop | Claude 回答完畢時 | 能,阻止 Claude 停下,對話繼續 |
ConfigChange | 工作階段期間某個配置檔案發生變化時 | 能,阻止這次變更生效(policy_settings 除外) |
CwdChanged | 工作目錄變化時,例如 Claude 執行了 cd 命令 | 不能。只把 stderr 顯示給使用者 |
PreCompact | 上下文壓縮之前 | 能,阻止壓縮 |
SessionEnd | 工作階段結束時 | 不能。只把 stderr 顯示給使用者 |
四個可以直接貼上的示例
每個都是 Anthropic 指南里現成可用的配置塊。Bash 示例用 jq 解析 JSON:macOS 上用 brew install jq 安裝,Debian 和 Ubuntu 上用 apt-get install jq,也可以看 jq 的下載頁。
Claude 需要輸入時收到通知
這個 hook 用 Notification 事件,Claude Code 在 Claude 等待輸入或權限時觸發它。macOS 的寫法在上一節裡;下面是 Linux 的,用 notify-send。加到 ~/.claude/settings.json:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
}
]
}
]
}
}
空的 matcher 對所有通知型別都觸發。只想在特定情況觸發,把它設成某個通知型別,例如 permission_prompt(Claude 需要你批准一次工具使用)或 idle_prompt(Claude 大約 60 秒前回答完,之後你一直沒有輸入)。macOS 上沒有通知時:osascript 透過內建的“指令碼編輯器”(Script Editor)應用發通知,如果指令碼編輯器沒有通知權限,命令會靜默失敗。Linux 上,notify-send 需要桌面通知常駐程式,無頭伺服器、SSH 工作階段和大多數容器都沒有。
編輯後自動格式化程式碼
這個 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 往一個 JavaScript 檔案里加一行帶單引號字串的程式碼,然後開啟檔案:用 Prettier 的預設設定,hook 會把它們改成雙引號。hook 成功時,Claude Code 不會在對話裡顯示任何東西,所以要看被編輯的檔案有沒有被重新格式化。Bash 命令也可能改寫檔案;想讓某個特定檔案不管怎麼變化都被重新格式化,改用 FileChanged hook。
攔截對受保護檔案的編輯
這個示例阻止 Claude 修改敏感檔案,比如 .env、package-lock.json,或者 .git/ 裡的任何東西。Claude 會收到說明編輯為什麼被攔截的反饋,從而調整做法。hook 呼叫一個指令碼,指令碼拿目標檔案路徑和一組受保護的模式比對,並用退出碼 2 攔截編輯。把指令碼存成 .claude/hooks/protect-files.sh:
#!/bin/bash
# protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Normalize Windows backslash separators so the patterns below match
FILE_PATH="${FILE_PATH//\\//}"
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
hook 指令碼必須可執行,Claude Code 才能執行它。在 macOS 和 Linux 上:
chmod +x .claude/hooks/protect-files.sh
然後在 .claude/settings.json 里加一個 PreToolUse hook,在任何 Edit 或 Write 工具呼叫之前執行這個指令碼:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
測試方法:讓 Claude 往你的 .env 檔案里加一行註釋。Claude Code 會在編輯執行之前攔截它,並把指令碼的 Blocked: 訊息作為反饋交給 Claude。
壓縮之後重新注入上下文
Claude 的上下文視窗滿了以後,壓縮(compaction)會把對話總結一遍來騰出空間,這可能丟掉重要細節。用帶 compact matcher 的 SessionStart hook,在每次壓縮之後重新注入關鍵上下文。你的命令寫到 stdout 的純文字,Claude Code 會加進 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 顯示最近的提交。
退出碼,以及攔截是怎麼生效的
Hooks 透過 stdin、stdout、stderr 和退出碼與 Claude Code 通訊。事件觸發時,Claude Code 把和事件相關的資料以 JSON 傳給你指令碼的 stdin。對一條 Bash 命令,PreToolUse hook 收到的欄位包括 session_id、cwd、hook_event_name、tool_name 和 tool_input,其中 tool_input.command 是那條 shell 命令。下面這個指令碼攔截一條命令:
#!/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 becomes Claude's feedback
exit 2 # exit 2 = block the action
fi
exit 0 # exit 0 = no decision; the normal permission flow applies
- 退出碼 0 表示成功。
PreToolUsehook 退出碼 0 時,這次工具呼叫仍然走正常的權限流程,因為退出碼 0 並不等於批准。對UserPromptSubmit、UserPromptExpansion、SessionStart和PostModelSwitchhooks,Claude Code 會把純文字 stdout 加進 Claude 的上下文。對其他大多數事件,stdout 寫進除錯日誌,不會顯示在對話記錄裡。 - 退出碼 2 在可以攔截的事件上阻止操作。把原因寫到 stderr:隨攔截一起給出的訊息就是 hook 的 stderr。取決於事件,Claude 會把它當作反饋來調整,或者使用者能看到它,或者根本不顯示訊息。有些事件不能攔截:對
SessionStart等,退出碼 2 只把 stderr 顯示給使用者,執行繼續。 - 其他任何退出碼是非阻塞錯誤:操作照常進行,但失敗即攔截的 hook 除外,例如把
onFailure設為"block"的 hook。在PreToolUse等事件上,你會在對話記錄裡看到一條<hook name> hook error提示。
退出碼為 0 的 hook,Claude 永遠看不到它的 stderr。想從 PostToolUse hook 向 Claude 傳一條警告,改用退出碼 2,這樣即使工具已經執行過,Claude 也能看到 stderr。PostToolUse hook 無法撤銷已經發生的動作,因為工具已經執行了。
退出碼只能讓你攔截或保持沉默。想要更細的控制,退出碼用 0,並往 stdout 列印一個 JSON 物件。每個 hook 只選一種方式:退出碼 2 加 stderr 訊息來攔截,或者退出碼 0 加 JSON 來做結構化控制。例如,PreToolUse hook 可以拒絕一次工具呼叫並告訴 Claude 原因:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Use rg instead of grep for better performance"
}
}
返回 "deny" 時,Claude Code 取消這次工具呼叫,並把 permissionDecisionReason 反饋給 Claude。返回 permissionDecision: "deny" 的 hook,即使在 bypassPermissions 模式下或帶 --dangerously-skip-permissions 時,也會攔截工具。多個 hook 匹配同一個事件時,每個 hook 的命令都會執行完,Claude Code 才合併結果,所以某個 hook 返回 deny 並不會阻止同級的其他 hook 執行。
怎麼除錯 hook
- 執行
/hooks,確認 hook 出現在正確的事件下面。 - 把一段示例 JSON 透過管道餵給指令碼來測試,再檢查退出碼(命令見下方)。
- 按
Ctrl+O開啟對話記錄檢視,找 hook 的結果。成功時什麼都看不到,除非 hook 的 JSON 帶出了什麼內容。阻塞錯誤會顯示隨攔截給出的訊息。非阻塞錯誤會顯示一條<hook name> hook error提示和一句簡短說明。 - 要看完整的執行細節,包括 hook 的退出碼、stdout 和 stderr,讀除錯日誌。用
claude --debug-file /tmp/claude.log啟動 Claude Code,把日誌寫到一個已知路徑,再在另一個終端裡tail -f /tmp/claude.log。如果啟動時沒加這個引數,在工作階段中執行/debug開啟日誌並找到日誌路徑。
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh
echo $? # Check the exit code
claude --debug-file /tmp/claude.log
tail -f /tmp/claude.log
常見問題排查
| 現象 | 檢查什麼 |
|---|---|
| hook 從不觸發 | 執行 /hooks,確認它出現在正確的事件下。檢查 matcher 是否和工具名完全一致,matcher 區分大小寫。確認你觸發的是對的事件型別:PreToolUse 在工具執行前觸發,PostToolUse 在之後觸發。 |
/hooks 裡沒有 hooks | 幾秒鐘後還沒出現,就重啟工作階段強制重新載入。確認 JSON 合法:不允許尾逗號和註釋。確認檔案位置正確:專案 hooks 用 .claude/settings.json,全域性 hooks 用 ~/.claude/settings.json。 |
| 對話記錄裡出現 “PreToolUse hook error: ...” | 你的指令碼意外地以非零退出碼退出。往它裡面管道輸入示例 JSON 手動測試。指令碼根本沒執行時,給它加可執行權限:chmod +x ./my-hook.sh。 |
| “command not found” | 引用指令碼時用絕對路徑或 ${CLAUDE_PROJECT_DIR}。 |
| “jq: command not found” | 安裝 jq,或者改用 Python/Node.js 解析 JSON。 |
| hook 列印了 JSON 卻沒有效果 | 可能有別的東西先寫了 stdout,通常是 shell 配置檔案裡無條件的 echo,輸出就不再以 { 開頭。把這些 echo 包起來,只在互動式 shell 裡執行。也可能是欄位放錯了層級:permissionDecision 屬於 hookSpecificOutput 內部,不是頂層。用 claude --debug 啟動,在除錯日誌裡搜 Hook output does not start with { 或 Hook JSON output had unrecognized keys。 |
| Claude 一直在幹活,並警告 Stop hook 攔截次數太多 | Stop hook 在連續攔截八次、期間 Claude 沒有任何工具呼叫時,Claude Code 會推翻它。從 JSON 輸入裡解析 stop_hook_active 欄位,如果是 true 就提前退出。 |
stop_hook_active 的檢查寫法如下:
#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # Allow Claude to stop
fi
# ... rest of your hook logic
常見問題
什麼時候該用 hook,而不是在 CLAUDE.md 裡寫一條指令?
某個動作必須一定發生時用 hook。hook 是 Claude Code 在特定時刻執行的 shell 命令,不依賴 LLM 自己決定要不要執行。想在每次工作階段開始時注入上下文,Anthropic 的指南建議改用 CLAUDE.md;壓縮之後重新注入上下文的例子,則是帶 compact matcher 的 SessionStart hook。
hook 該放進哪個設定檔案?
對你所有專案生效、又只留在你機器上的 hook,放 ~/.claude/settings.json。只針對一個專案、可以提交進倉庫的,放 .claude/settings.json。只針對一個專案又不想共享的,放 .claude/settings.local.json。不同層級的 hook 條目是合併的,不會互相替換。
在繞過權限的模式下,hook 還能攔截嗎?
能。返回 permissionDecision: "deny" 的 PreToolUse hook,即使在 bypassPermissions 模式下或帶 --dangerously-skip-permissions 時也會攔截工具,因為 PreToolUse hooks 在任何權限模式檢查之前觸發。返回 "allow" 的 hook 繞不過設定裡的 deny 規則。
來源
- Claude Code:Automate actions with hooks(第一個 hook、示例、matcher、排錯,2026 年 10 月 30 日核對)
- Claude Code:Hooks reference(hook 位置、handler 欄位、退出碼、除錯、安全注意事項,2026 年 10 月 30 日核對)