← 所有文章

Claude Code 鉤子詳解:包覆代理程式的確定性層

出自指南: Claude Code Comprehensive Guide

什麼是 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,就從快速上手開始。


  1. Anthropic,「Hooks reference — Hook lifecycle and hook events」。code.claude.com/docs/en/hooks#hook-events ↩↩↩↩↩↩

  2. 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 ↩↩↩↩↩↩↩↩

  3. Anthropic,「Automate actions with hooks」。code.claude.com/docs/en/hooks-guide ↩↩↩

  4. Anthropic,「Hooks guide — Hooks and permission modes」。code.claude.com/docs/en/hooks-guide#hooks-and-permission-modes ↩↩

  5. Anthropic,「Hooks reference — JSON output and decision control」。code.claude.com/docs/en/hooks#json-output ↩↩↩↩↩↩↩

  6. Anthropic,「Hooks reference — Configuration: hook locations, matcher patterns, hook handler fields, the /hooks menu」。code.claude.com/docs/en/hooks#configuration ↩↩↩↩↩↩↩

  7. Anthropic,「Hooks guide — Limitations and troubleshooting」。code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting ↩↩↩↩↩

  8. Anthropic,「Hooks reference — Security considerations」。code.claude.com/docs/en/hooks#security-considerations ↩

  9. Anthropic,「Claude Code settings」。code.claude.com/docs/en/settings ↩

相關文章

Codex CLI vs Claude Code 2026:架構、定價與中國存取

2026 年的 Codex CLI vs Claude Code:核心層沙箱、hook 治理、模型上下文、定價、中國雲端存取,以及各自的適用時機。

28 分鐘閱讀

Claude Code Hooks:我的 95 個 Hook 為何各自存在

我為 Claude Code 建立了 95 個 hook。每一個都源於某次出錯的經驗。以下是它們的起源故事以及逐漸成形的架構。

7 分鐘閱讀