Claude Code 鉤子詳解:包覆代理程式的確定性層
什麼是 Claude Code 鉤子? 鉤子是使用者自訂的 shell 指令(也包含 HTTP 端點、MCP 工具與模型提示詞),Claude Code 會在生命週期的固定節點自動執行它們:工具呼叫之前、編輯之後、工作階段開始時,以及 Claude 回答結束時。1 CLAUDE.md 給模型的是它多半會遵守的指示,而鉤子無論模型是否配合都會執行。在任一工作階段中輸入 /hooks,即可看到全部生命週期事件,以及各自掛了什麼。
多數開發者以兩層控制來執行 Claude Code:權限約束代理程式被允許做什麼,CLAUDE.md 描述它應該做什麼。鉤子是第三層,也是唯一能保證任何事情的一層。以下依序說明:心智模型、現行文件中的每一個生命週期事件、精確的輸入輸出契約、設定方式、五個可用模式,以及一套取捨框架。所有 API 細節都已對照 2026年8月8日的官方鉤子參考文件與指南查證過。這套機制演進很快,因此當本文與參考文件有出入時,以參考文件為準。(初次接觸 Claude Code?不妨先看 5 分鐘上手或 Claude Code 入門路徑。)
TL;DR: 鉤子從 stdin 接收 JSON,並以結束碼或 stdout 上的 JSON 作答。exit 0 代表放行,exit 2 代表攔截(限於支援攔截的事件),而 exit 1 這個 Unix 慣用的失敗碼什麼都攔不住——這是鉤子最大的一個陷阱。2 設定寫在 settings.json 中 PreToolUse、Stop 這類事件名稱底下,再用 matcher 篩選。凡是非發生不可的事情交給鉤子,凡是模型知道即可的事情交給 CLAUDE.md。
心智模型:為非確定性核心套上保證
程式撰寫代理程式是一套機率系統。要它每次編輯後都跑一遍 Prettier,它多半會照做。但當改動看起來無足輕重、上下文變得很長,或是你的措辭被理解成別的意思時,它就可能跳過這一步。CLAUDE.md、技能與提示詞全都只是建議:品質很高、通常被採納,卻永遠得不到保證。
鉤子是包覆在這個核心外面的確定性外殼。指南開頭給了一句話的定義——「鉤子是使用者自訂的 shell 指令」——並把重點講得很直白:鉤子提供的是「確定性控制:某些動作總是會發生,而不是仰賴 LLM 自己選擇去執行」。3(指南這一句低估了目前的實際範圍:參考文件中更完整的定義已經納入 HTTP 端點與 LLM 提示詞,處理常式還可以是 MCP 工具,詳見下文的設定一節。)格式化工具會在每次編輯時觸發。指令守衛會評估每一次 Bash 呼叫。完成關卡會檢查每一次收尾。
這種強制力是真的,不是裝飾:PreToolUse 鉤子在任何權限模式檢查之前觸發,所以一個回傳 permissionDecision: "deny" 的鉤子,即使在 bypassPermissions 模式下、或帶著 --dangerously-skip-permissions 執行,照樣攔得住工具。反過來則不成立——回傳 "allow" 的鉤子無法放寬設定裡的 deny 規則。鉤子只能把政策收得比權限更緊,絕不能把它放鬆。4
生命週期:全部鉤子事件
截至 2026年8月8日,參考文件記載了 31 個鉤子事件。1 它們分屬三種節奏:每個工作階段一次(SessionStart、SessionEnd)、每一輪對話一次(UserPromptSubmit、Stop、StopFailure),以及代理迴圈內每次工具呼叫都觸發(PreToolUse、PostToolUse)。其餘的則在特定條件下觸發——設定變更、上下文壓縮、子代理程式、MCP 互動等。
| 事件 | 觸發時機 | 一個真實用途 |
|---|---|---|
SessionStart |
工作階段開始或恢復時 | 把 git 分支與未結的 issue 注入為上下文 |
Setup |
--init-only,或 -p 模式下的 --init/--maintenance |
在代理程式執行前於 CI 中安裝相依套件 |
UserPromptSubmit |
你送出提示詞後、Claude 處理之前 | 附上目前日期;拒絕含有機密資訊的提示詞 |
UserPromptExpansion |
輸入的指令展開成提示詞時 | 稽核或否決技能與指令的展開結果 |
PreToolUse |
工具呼叫執行之前 | 攔截破壞性的 shell 指令 |
PermissionRequest |
跳出權限對話框時 | 自動核准可信指令,省去逐次確認 |
PermissionDenied |
自動模式分類器拒絕了某次工具呼叫 | 回傳 retry: true,讓模型可以重試 |
PostToolUse |
工具呼叫成功之後 | 自動格式化每一個被編輯的檔案 |
PostToolUseFailure |
工具呼叫失敗之後 | 記下失敗的指令以便排查 |
PostToolBatch |
一批平行工具呼叫之後、下一次模型呼叫之前 | 為代理迴圈設檢查點或直接中止 |
Notification |
Claude Code 送出通知時 | Claude 需要輸入時跳出桌面通知 |
MessageDisplay |
助理訊息內文顯示期間 | 在螢幕上遮蔽(僅影響顯示,轉錄記錄不變) |
SubagentStart |
子代理程式被建立時 | 依代理程式類型注入專屬上下文 |
SubagentStop |
子代理程式結束時 | 在結果回傳前驗證子代理程式的輸出 |
TaskCreated |
透過 TaskCreate 建立任務時 |
強制執行任務命名或範圍規則 |
TaskCompleted |
任務被標記為完成時 | 在完成生效前核驗驗收標準 |
Stop |
Claude 回答結束時 | 完成關卡:測試沒過就不准收尾 |
StopFailure |
本輪因 API 錯誤而結束 | 對 rate_limit 或 billing_error 發出警示(僅記錄,輸出會被忽略) |
TeammateIdle |
代理程式團隊中的隊友即將進入閒置 | 用佇列讓隊友持續有事可做 |
InstructionsLoaded |
CLAUDE.md 或 .claude/rules/*.md 檔案被載入上下文時 |
記錄哪些指示進入了本次工作階段 |
ConfigChange |
工作階段進行中設定檔發生變動 | 攔截未經許可的設定修改 |
CwdChanged |
工作目錄改變時 | 重新載入 direnv 式的環境 |
DirectoryAdded |
工作階段進行中透過 /add-dir 或 SDK 的 register_repo_root 註冊工作目錄時(v2.1.219 以上) |
該儲存庫一加入就立刻載入它的上下文 |
FileChanged |
被監看的檔案在磁碟上變動時 | .env 變更時重新載入環境變數 |
WorktreeCreate |
透過 --worktree 或 isolation: "worktree" 建立 worktree 時 |
取代預設的 git worktree 準備流程 |
WorktreeRemove |
worktree 被移除時 | 在工作階段或子代理程式結束時執行自訂清理 |
PreCompact |
上下文壓縮之前 | 保存那些丟不起的狀態 |
PostCompact |
壓縮完成之後 | 重新注入關鍵上下文 |
Elicitation |
MCP 伺服器要求使用者輸入時 | 在無頭執行中自動填寫表單 |
ElicitationResult |
你回答了 MCP 的 elicitation 之後 | 在回應送出前驗證或覆寫它 |
SessionEnd |
工作階段終止時 | 封存記錄檔、釋放資源 |
其中絕大多數你都用不到。幾乎每一套上線的設定都建立在五個事件之上:PreToolUse、PostToolUse、UserPromptSubmit、SessionStart 與 Stop。其餘的之所以存在,是為了你真的需要它們的那一天。
契約:輸入 JSON,輸出結束碼或 JSON
指令型鉤子從 stdin 接收 JSON,並透過結束碼、stdout 與 stderr 作答。(HTTP 鉤子以 POST 內文接收同樣的 JSON,並透過回應內文作答。)2
每個事件都會送來一個共通信封——session_id、transcript_path、cwd 與 hook_event_name,多數事件還帶 permission_mode——再加上各事件特有的欄位。一個針對 Bash 指令的 PreToolUse 鉤子收到的是:
{
"session_id": "abc123",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "npm test" }
}
其他事件只是換掉後半段:UserPromptSubmit 帶 prompt,SessionStart 帶 source(startup/resume/clear/compact/fork——第五個值隨 v2.1.214 的分叉工作階段一起到來,從舊的四值清單抄來的 source 比對鉤子會無聲無息地漏掉分叉),Stop 帶 stop_hook_active 與 last_assistant_message。在子代理程式內部觸發的鉤子還會額外收到 agent_id 與 agent_type。2
結束碼
結果只有三種:2
- Exit 0 —— 成功。Claude Code 會解析 stdout 中的 JSON 輸出欄位。對多數事件而言,stdout 只進偵錯記錄;但對
UserPromptSubmit、UserPromptExpansion與SessionStart,純文字 stdout 會作為 Claude 看得到的上下文加入。 - Exit 2 —— 攔截性錯誤。stdout(連同其中的任何 JSON)會被忽略,stderr 則作為錯誤訊息回傳給 Claude。「攔截」究竟是什麼意思,取決於事件本身。
- 其他任何結束碼 —— 非攔截性錯誤。轉錄記錄中會出現一則
<hook name> hook error提示,執行照常繼續。
最後這一條值得加粗:exit 1 什麼都攔不住。 文件對此有直接的警告——儘管 1 是 Unix 慣用的失敗碼,Claude Code 仍把 exit 1 當成非攔截性錯誤並繼續往下走。執行政策的鉤子必須用 exit 2。2
exit 2 在各事件上的效果如下:2
| 事件 | exit 2 的效果 |
|---|---|
PreToolUse |
攔下這次工具呼叫 |
PermissionRequest |
拒絕該項權限 |
UserPromptSubmit |
阻擋處理並清除提示詞 |
UserPromptExpansion |
阻擋這次展開 |
Stop / SubagentStop |
阻止停止,對話繼續 |
TeammateIdle |
阻止隊友進入閒置 |
TaskCreated / TaskCompleted |
復原建立動作 / 阻止完成 |
ConfigChange |
攔下該次設定變更(policy_settings 除外) |
PreCompact |
阻擋上下文壓縮 |
PostToolBatch |
在下一次模型呼叫前停住代理迴圈 |
Elicitation / ElicitationResult |
拒絕該次 elicitation / 把回應變成婉拒 |
WorktreeCreate |
任何非零結束碼都會中止 worktree 的建立 |
其餘事件都無法攔截。PostToolUse 與 PostToolUseFailure 會把 stderr 顯示給 Claude(工具已經跑完了);SessionStart、Notification、SessionEnd、CwdChanged、FileChanged、PostCompact、SubagentStart 與 Setup 只把 stderr 顯示給使用者,而 DirectoryAdded 只把 stderr 送進偵錯記錄;StopFailure、InstructionsLoaded、MessageDisplay 與 PermissionDenied 則完全忽略結束碼——對 PermissionDenied 來說,唯一的施力點是 JSON 裡的 retry: true。2
JSON 輸出
若你需要比「攔截或沉默」更細緻的控制,就以 exit 0 結束並向 stdout 印出一個 JSON 物件。先講一條鐵律:要嘛結束碼、要嘛 JSON,不能兩者並用——JSON 只在 exit 0 時被處理,exit 2 會把它丟棄。5
通用欄位在所有事件上都有效:continue: false 會徹底停下 Claude(並向使用者顯示 stopReason),suppressOutput 把 stdout 從轉錄記錄中隱藏,systemMessage 向使用者顯示一則警告,terminalSequence 則發出允許清單內的終端機逸出序列(桌面通知、視窗標題、響鈴)。決策欄位則因事件而異:5
| 事件 | 決策形態 | 關鍵欄位 |
|---|---|---|
UserPromptSubmit、UserPromptExpansion、PostToolUse、PostToolUseFailure、PostToolBatch、Stop、SubagentStop、ConfigChange、PreCompact |
頂層 decision |
decision: "block" 加 reason(顯示給 Claude)。要放行就省略 decision |
PreToolUse |
hookSpecificOutput |
permissionDecision:"allow" | "deny" | "ask" | "defer",另有 permissionDecisionReason,以及在執行前改寫工具參數的 updatedInput |
PermissionRequest |
hookSpecificOutput |
decision.behavior:"allow" | "deny",可選的 decision.updatedInput |
PermissionDenied |
hookSpecificOutput |
retry: true 告訴模型可以重試 |
PostToolUse |
hookSpecificOutput |
updatedToolOutput 會取代工具的回傳結果 |
Stop / SubagentStop |
hookSpecificOutput |
additionalContext:非錯誤性的回饋,讓對話繼續而不算成鉤子錯誤 |
SessionStart、Setup、SubagentStart |
僅上下文 | additionalContext,以及 SessionStart 專屬的 initialUserMessage、sessionTitle、watchPaths、reloadSkills。無法攔截 |
MessageDisplay |
hookSpecificOutput |
displayContent 只取代螢幕上的文字 |
Elicitation / ElicitationResult |
hookSpecificOutput |
action:"accept" | "decline" | "cancel",以及 content |
TeammateIdle、TaskCreated、TaskCompleted |
通用的 continue |
continue: false 加 stopReason 會徹底終止隊友或任務的流程(按事件的攔截仍然是 exit 2) |
WorktreeCreate |
回傳路徑 | 指令型鉤子把 worktree 路徑印到 stdout;HTTP 鉤子回傳 hookSpecificOutput.worktreePath;失敗或缺少路徑都會讓建立失敗 |
WorktreeRemove、Notification、SessionEnd、PostCompact、InstructionsLoaded、StopFailure、CwdChanged、DirectoryAdded、FileChanged |
無 | 只有副作用 |
有兩個細節最容易讓人栽跟頭。其一,PreToolUse 是頂層 decision 模式的例外:它在歷史上確實用頂層的 decision/reason,但這兩個欄位在該事件上已經棄用("approve"/"block" 分別對應 "allow"/"deny"),請改用 hookSpecificOutput.permissionDecision。5 其二,當多個 PreToolUse 鉤子的判斷彼此衝突時,優先順序是 deny > defer > ask > allow——最嚴格的答案勝出。即便如此,也別倚賴這套平手規則,最好讓每個決策都只有一個負責的鉤子。5
設定:settings.json、matcher 與作用範圍
鉤子設定分成三層巢狀:挑一個事件,加一個matcher 群組來篩選觸發時機,再定義一個或多個要執行的鉤子處理常式。6
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "/path/to/lint-check.sh" }
]
}
]
}
}
放在哪裡決定了作用範圍:~/.claude/settings.json 對你所有專案生效,.claude/settings.json 屬於專案層級且可以提交,.claude/settings.local.json 屬於專案層級但被 gitignore 排除;優先順序沿用標準的設定層級——受管政策高於本機,本機高於專案,專案高於使用者。9 鉤子也可以隨外掛(hooks/hooks.json)以及技能或代理程式的 frontmatter 一起提供,而企業管理員可以強制下發使用者無法覆寫的受管鉤子。6
matcher 依其字元組成來判定:"*"、"" 或省略 matcher 表示比對所有項目;只含字母、數字、_、-、空格、逗號與 | 的值是精確字串或清單(Bash、Edit|Write);其他寫法都會被當成不帶錨點的 JavaScript 正規表示式,因此 Edit.* 會同時比對到 Edit 與 NotebookEdit——只想指定單一工具時,請用 ^Edit$ 加上錨點。matcher 區分大小寫,而且每個事件比對的是各自的欄位:工具事件看工具名稱,SessionStart 看 source,SubagentStart 看代理程式類型,Notification 看通知類型。6 若想對工具事件做更精準的篩選,處理常式層級的 if 欄位可以接受一條權限規則,例如 "Bash(git *)"——但它是盡力而為的(遇到無法解析的指令會直接放行),所以真正需要硬性保證時,請用權限規則而不是 if。6 還有一處語意變更值得知道:自 v2.1.214 起,if 中的單段路徑樣式(例如 Edit(src/**))只會比對到工作目錄底下最上層的 src;在該版本之前寫下的 if,會悄悄不再比對 packages/app/src/ 這類巢狀路徑——想要過去那種不限深度的行為,請寫成 Edit(**/src/**)。6
處理常式共有五種類型:command(shell)、http(POST 端點)、mcp_tool、prompt(單輪模型評估)與 agent(一個具備 Read/Grep/Glob 權限的子代理程式,實驗性)。預設逾時時間:command/http/mcp_tool 為 600 秒(UserPromptSubmit 降為 30 秒,MessageDisplay 降為 10 秒),prompt 為 30 秒,agent 為 60 秒——可用 timeout 逐一覆寫。6 所有比對到的鉤子會平行執行,完全相同的處理常式會被去除重複,而 $CLAUDE_PROJECT_DIR 讓指令碼指向你的專案根目錄。
用 /hooks 來核對:這是一個唯讀的瀏覽介面,會列出每個事件、其上設定的鉤子,以及每一條各自來自哪個設定檔。要更動就去編輯 JSON(或請 Claude 代勞)。想暫時全部關掉,設定 "disableAllHooks": true。6
五個模式
都做了通用化與最小化處理。鉤子教學把其中幾個做成更完整的上線版本,給 Apple 開發用的鉤子則把它們套用到 iOS 工具鏈上。
1. 編輯後自動格式化(PostToolUse)
直接取自官方指南——Claude 碰過的每一個檔案都會被格式化,毫無例外:3
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}
依你的技術堆疊把指令換成 ruff format、gofmt 或 swiftformat 即可。
2. 攔截危險指令(PreToolUse,exit 2)
#!/bin/bash
# .claude/hooks/guard-bash.sh — register on PreToolUse, matcher "Bash"
command=$(jq -r '.tool_input.command // empty')
case "$command" in
*"rm -rf"* | *"git push --force"* | *"DROP TABLE"*)
echo "Blocked: matches a destructive pattern. Propose a safer alternative." >&2
exit 2 ;;
esac
exit 0
exit 2 會攔下這次呼叫,並把 stderr 回傳給 Claude,於是它會調整方向,而不是盲目重試。用 JSON 做到同樣的事,就是帶上理由的 permissionDecision: "deny",而且這條路還留有往 "ask"(交給人來裁決)或 updatedInput(改寫指令)演進的餘地。5
3. 在工作階段開始時注入上下文(SessionStart)
SessionStart 鉤子的純文字 stdout 會直接成為 Claude 看得到的上下文——不需要 JSON:1
#!/bin/bash
# .claude/hooks/session-context.sh — register on SessionStart
echo "Current branch: $(git branch --show-current)"
echo "Recent commits:"
git log --oneline -5
echo "Uncommitted files: $(git status --porcelain | wc -l | tr -d ' ')"
exit 0
這一招用於動態狀態。靜態的慣例應該寫進 CLAUDE.md,文件本身也建議:不需要指令碼的上下文,就交給 CLAUDE.md。1
4. 掛在 Stop 上的完成關卡
Stop 會在 Claude 回答結束時觸發。攔住它,就能逼著代理程式一路做到條件成立為止:
#!/bin/bash
# .claude/hooks/stop-gate.sh — register on Stop
input=$(cat)
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # already continuing because of this hook; don't loop forever
fi
if ! npm test --silent > /tmp/stop-gate.log 2>&1; then
jq -n '{decision: "block", reason: "Tests are failing. Fix them before finishing. Log: /tmp/stop-gate.log"}'
fi
exit 0
stop_hook_active 這項檢查很關鍵:Claude Code 預設把 Stop 鉤子的連續攔截限制在 8 次(可用 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP 調高),而一個從不檢查自己是否已經觸發過續跑的關卡,會一口氣把額度燒光。7 想要更柔和的引導,就回傳 hookSpecificOutput.additionalContext 而不是 decision: "block"——同樣讓對話繼續,但性質是帶標籤的回饋,而非鉤子錯誤。至於一次性的條件,內建的 /goal 指令本身就是一個零設定、工作階段層級、以提示詞為基礎的 Stop 鉤子。1
5. 分派器:一個入口,許多小鉤子
註冊十個鉤子,意味著十條會在不同機器與專案之間逐漸走樣的 settings.json 設定。另一種做法是:每個事件只註冊一個分派器,然後依慣例路由。
#!/bin/bash
# .claude/hooks/dispatch.sh — register once per event you care about
input=$(cat)
event=$(echo "$input" | jq -r '.hook_event_name')
dir="$CLAUDE_PROJECT_DIR/.claude/hooks/$event"
[ -d "$dir" ] || exit 0
for hook in "$dir"/*.sh; do
[ -x "$hook" ] || continue
echo "$input" | "$hook" || exit $?
done
exit 0
現在,加一個守衛只需要在 .claude/hooks/PreToolUse/ 裡放一個新檔案並 chmod +x——settings.json 原封不動,每個指令碼都小到可以單獨測試,而且第一個 exit 2 會照常往上傳遞。有一點要注意:分派器把 Claude Code 原本會平行執行的東西改成串列執行,而且它最適合結束碼型的鉤子——會輸出 JSON 的鉤子應該單獨註冊,因為 stdout 裡必須恰好只有一個 JSON 物件。5
鉤子、CLAUDE.md、技能與記憶的分工
四種機制,四種職責:
| 機制 | 職責 | 選擇標準 |
|---|---|---|
| 鉤子 | 強制 | 如果「跳過」必須是不可能的(格式化、安全、關卡),那就用鉤子 |
| CLAUDE.md | 引導 | 如果是模型每次工作階段都該知道的慣例(技術堆疊、風格、指令),那就寫進 CLAUDE.md |
| 技能 | 能力 | 如果是一套自帶說明與指令碼、在相關場合被叫用的流程,那就是技能 |
| 記憶 | 回想 | 如果是某次工作階段學到、往後工作階段還要用到的事實,那就是記憶 |
失敗會朝兩個方向發生。把慣例寫成鉤子,換來的是一堆脆弱的指令碼,去強制那些一句話引導就能搞定的事。把政策寫成 CLAUDE.md 裡的散文,換來的是一個偏偏在關鍵那天對 main 強制推送的代理程式。判斷標準是:模型就算只忽略這一次,代價是什麼?只是添麻煩,就歸 CLAUDE.md;會變成事故,就歸鉤子。
鉤子做不到的事
以下都是官方文件給出的誠實邊界:7
- 鉤子無法叫用工具或斜線指令。 指令型鉤子只會說三種話:stdout、stderr 與結束碼。經由
additionalContext回傳的上下文會以純文字注入。 PostToolUse無法復原。 工具已經跑過了。防患未然要靠PreToolUse。Stop在每次回答結束時都會觸發,而不只是在「任務完成」時;使用者手動中斷時它不會觸發(API 錯誤觸發的是StopFailure)。關卡邏輯必須容得下任務中途的停止。PermissionRequest在單純的無頭(-p)執行中不會觸發。 但當 Agent SDK 的canUseTool回呼提供了提示時,它在-p下確實會觸發,背景子代理程式的工具呼叫也會觸發;其餘自動化情境請用PreToolUse。PreToolUse看不到以@引用的檔案。 提示詞裡透過@拉進來的檔案並不涉及工具呼叫;要擋住這條路徑,請用Read的 deny 規則保護相應路徑。1- 平行情況下的
updatedInput在設計上就不可靠。 當多個 PreToolUse 鉤子改寫同一個工具的參數時,只有一次改寫會留下來,而且你無法決定是哪一次。讓每次改寫都只歸屬一個鉤子。 - 逾時會取消鉤子。 指令型鉤子預設 600 秒(
UserPromptSubmit為 30 秒,MessageDisplay為 10 秒);一個會逾時的慢關卡,等於一個根本沒跑過的關卡。 - 輸出上限為 10,000 個字元——超出的部分會寫入檔案,並以預覽內容取代。
- 鉤子以你完整的使用者權限執行。 參考文件自己的警告是:它們「可以修改、刪除或存取你的使用者帳戶所能存取的任何檔案。在把鉤子指令加入設定之前,請先審閱並測試它們」。8 為變數加上引號、使用絕對路徑、避開敏感檔案。
- 一個壞掉的鉤子會拖垮之後的每一次工作階段,直到你修好為止。可以用轉錄檢視(
Ctrl+O)、claude --debug-file /tmp/claude.log,或工作階段中途的/debug來排查;一個經典的陷阱是:shell 設定檔在啟動時印出了東西,把鉤子的 JSON 輸出弄壞了。7
常見問題
什麼是 Claude Code 鉤子?
鉤子是使用者自訂的指令——shell 指令碼、HTTP 端點、MCP 工具或模型提示詞——由 Claude Code 在特定的生命週期節點自動執行。3 它們從 stdin 接收事件 JSON,並以結束碼或 JSON 作答:攔下一次工具呼叫、注入上下文、改寫參數、讓代理程式繼續工作。與 CLAUDE.md 裡的指示不同,它們每次都會執行,與模型的行為無關。
PreToolUse 鉤子和權限有什麼不同?
權限規則是宣告式的:由 Claude Code 自己評估的靜態 allow/deny/ask 樣式。PreToolUse 鉤子則是可程式化的:由你的程式碼檢視完整的工具輸入再做判斷。鉤子在權限模式檢查之前觸發,因此鉤子給出的 "deny" 即使在 bypassPermissions 模式下依然有效——但鉤子給出的 "allow" 無法推翻設定中的 deny 規則。4 凡是一條樣式就能表達的,交給權限規則;當判斷需要邏輯、外部狀態或改寫輸入時,再動用鉤子。
無頭(-p)模式下鉤子能用嗎?
能用,只有一處細微差別:PermissionRequest 鉤子會跳過單純的 -p 執行(那裡沒有東西提供權限提示),不過當 Agent SDK 的 canUseTool 回呼提供了提示時它會觸發,背景子代理程式的工具呼叫也會觸發。單純無頭執行中的自動權限判斷,應該放進 PreToolUse。7 無頭模式還解鎖了一個互動式工作階段會忽略的選項:permissionDecision: "defer",它會暫停一次工具呼叫,好讓外層的程序(一個 Agent SDK 應用程式、一個自訂介面)收集輸入,稍後再恢復工作階段。5
為什麼我的鉤子跑了卻什麼都沒攔住?
幾乎總是違反了契約。exit 1 不會攔截——只有 exit 2 才會,而且僅限支援攔截的事件。2 JSON 決策只在 exit 0 時被解析——一個先印出 {"decision": "block"} 再以 exit 2 結束的指令碼,它的 JSON 會被丟棄。另外 matcher 區分大小寫——bash 永遠比對不到 Bash。先用 /hooks 確認註冊無誤,再把範例 JSON 透過管線餵給指令碼並檢查 echo $?。7
參考來源
已對照 2026年8月8日的官方文件查證。鉤子 API 在 Claude Code v2.1.x 的各次發布中發生過實質變化(新事件、新欄位、matcher 語意),因此請把與版本相關的細節視為「截至該日期」的情況。
本站相關內容:想看包含提示詞鉤子與代理程式鉤子在內的完整系統視角,請讀 Claude Code 指南的鉤子章節;想要五個附完整設定的上線級實作,請讀鉤子教學;想看落到 iOS 上的應用模式,請讀給 Apple 開發用的鉤子;如果你還沒安裝 Claude Code,就從快速上手開始。
-
Anthropic,「Hooks reference — Hook lifecycle and hook events」。code.claude.com/docs/en/hooks#hook-events ↩↩↩↩↩↩
-
Anthropic,「Hooks reference — Hook input and output; exit code output; exit code 2 behavior per event」。code.claude.com/docs/en/hooks#exit-code-output ↩↩↩↩↩↩↩↩
-
Anthropic,「Automate actions with hooks」。code.claude.com/docs/en/hooks-guide ↩↩↩
-
Anthropic,「Hooks guide — Hooks and permission modes」。code.claude.com/docs/en/hooks-guide#hooks-and-permission-modes ↩↩
-
Anthropic,「Hooks reference — JSON output and decision control」。code.claude.com/docs/en/hooks#json-output ↩↩↩↩↩↩↩
-
Anthropic,「Hooks reference — Configuration: hook locations, matcher patterns, hook handler fields, the /hooks menu」。code.claude.com/docs/en/hooks#configuration ↩↩↩↩↩↩↩
-
Anthropic,「Hooks guide — Limitations and troubleshooting」。code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting ↩↩↩↩↩
-
Anthropic,「Hooks reference — Security considerations」。code.claude.com/docs/en/hooks#security-considerations ↩
-
Anthropic,「Claude Code settings」。code.claude.com/docs/en/settings ↩