Hooks 參考
Claude Code hook 事件、配置架構、JSON 輸入/輸出格式、退出代碼、非同步 hooks、HTTP hooks、提示 hooks 和 MCP 工具 hooks 的參考。
如需快速入門指南和範例,請參閱 使用 hooks 自動化工作流程。
Hooks 是使用者定義的 shell 命令、HTTP 端點、MCP 工具呼叫、LLM 提示或子代理,在 Claude Code 生命週期的特定時間點自動執行。Claude Code 在任何地方執行時都會觸發相同的 hook 事件:終端機中的工作階段、IDE 擴充功能、桌面應用程式 和 Claude Code 網頁版。使用此參考來查詢事件架構、配置選項、JSON 輸入/輸出格式,以及非同步 hooks、HTTP hooks 和 MCP 工具 hooks 等進階功能。
外掛程式也可以將 hooks 註冊為 JavaScript 函式,Claude Code 會在其自己的程序中呼叫這些函式,這些函式既可以在介面中繪製,也可以對事件進行操作。執行此操作的外掛程式是 mod,這些函式 hooks 在 對事件做出反應 中涵蓋,而不是在此處。此頁面上的 hooks 會繼續與 mods 一起運作。
Hook 生命週期
Claude Code 在工作階段期間的特定時間點執行 hooks。當事件觸發且匹配器符合時,Claude Code 會將有關該事件的 JSON 上下文傳遞給您的 hook 處理程式。對於命令 hooks,輸入會到達 stdin。對於 HTTP hooks,它會作為 POST 請求正文到達。您的處理程式可以檢查輸入、採取行動,並可選擇性地返回決定。
事件分為三種節奏:
- 每個工作階段一次:
SessionStart和SessionEnd - 每個轉向一次:
UserPromptSubmit、Stop和StopFailure - 在代理迴圈內每個工具呼叫上:
PreToolUse和PostToolUse,除了EndConversation呼叫外,兩者都會跳過
<img src="https://mintcdn.com/claude-code/x7pO8l4XcvAXCoVc/images/hooks-lifecycle-dark.svg?fit=max&auto=format&n=x7pO8l4XcvAXCoVc&q=85&s=c9b3d88487335f58cce0b52e2f9e7531" className="hidden dark:block" alt="Hook 生命週期圖表,顯示可選的 Setup 進入 SessionStart,然後是每個轉向的迴圈,包含 UserPromptSubmit、用於 slash commands 的 UserPromptExpansion、嵌套的代理迴圈(PreToolUse、PermissionRequest、PostToolUse、PostToolUseFailure、PostToolBatch、SubagentStart/Stop、TaskCreated、TaskCompleted)和 Stop 或 StopFailure,接著是 TeammateIdle、PreCompact、PostCompact 和 SessionEnd,Elicitation 和 ElicitationResult 嵌套在 MCP 工具執行內,PermissionDenied 作為 PermissionRequest 的側分支用於自動模式拒絕,WorktreeCreate、WorktreeRemove、Notification、ConfigChange、InstructionsLoaded、CwdChanged、FileChanged 和 DirectoryAdded 作為獨立非同步事件,PreModelSwitch 作為獨立順序事件,在請求的模型切換之前執行,PostModelSwitch 作為獨立非同步事件,在工作階段的模型變更後執行,以及 MessageDisplay 作為顯示專用事件,在助手訊息文字串流時執行" width="520" height="1336" data-path="images/hooks-lifecycle-dark.svg" />
下表總結了每個事件何時觸發。Hook 事件部分記錄了每個事件的完整輸入架構和決定控制選項。
| 事件 | 何時觸發 |
|---|---|
SessionStart |
當工作階段開始或繼續時 |
Setup |
當您使用 --init-only 啟動 Claude Code,或在 -p 模式中使用 --init 或 --maintenance 時。用於 CI 或指令碼中的一次性準備 |
UserPromptSubmit |
當提示詞被提交時,在 Claude 處理之前。也會在 Claude Code 自行開始的回合上觸發 |
UserPromptExpansion |
當使用者輸入的命令擴展為提示詞時,在到達 Claude 之前。可以阻止擴展 |
PreToolUse |
在工具呼叫執行之前。可以阻止它 |
PermissionRequest |
當工具呼叫需要權限決定時 |
PermissionDenied |
當自動模式拒絕工具呼叫時,包括沒有分類器判決的拒絕。使用 JSON hookSpecificOutput.retry: true 告訴模型它可能重試被拒絕的工具呼叫。Claude Code 在分類器未產生判決時忽略 retry |
PostToolUse |
在工具呼叫成功後 |
PostToolUseFailure |
在工具呼叫失敗後 |
PostToolBatch |
在完整的平行工具呼叫批次解決後,在下一個模型呼叫之前 |
Notification |
當 Claude Code 傳送通知時 |
MessageDisplay |
在助手訊息文字顯示時 |
SubagentStart |
當子代理被生成時 |
SubagentStop |
當子代理完成時 |
TaskCreated |
當透過 TaskCreate 建立任務時 |
TaskCompleted |
當任務被標記為已完成時 |
Stop |
當 Claude 完成回應時 |
StopFailure |
當回合因 API 錯誤而結束時 |
TeammateIdle |
當代理團隊隊友即將閒置時 |
InstructionsLoaded |
當 CLAUDE.md 或 .claude/rules/*.md 檔案被載入到上下文時。在工作階段開始時以及在工作階段期間延遲載入檔案時觸發 |
ConfigChange |
當設定檔在工作階段期間變更時 |
CwdChanged |
當工作目錄變更時,例如當 Claude 執行 cd 命令時。適用於使用 direnv 等工具進行反應式環境管理 |
DirectoryAdded |
當工作目錄在工作階段中期透過 /add-dir 或 SDK register_repo_root 控制請求新增時 |
FileChanged |
當監視的檔案在磁碟上變更時。matcher 欄位指定要監視的檔案名稱 |
WorktreeCreate |
當透過 --worktree、isolation: "worktree" 建立 worktree 時,或用於背景工作階段時。取代預設的 git 行為 |
WorktreeRemove |
當在工作階段結束時、子代理完成時或您刪除背景工作階段時移除 worktree 時 |
PreCompact |
在上下文壓縮之前 |
PostCompact |
在上下文壓縮完成後 |
PreModelSwitch |
在 Claude Code 應用您或用戶端要求的模型切換之前。可以阻止切換 |
PostModelSwitch |
在工作階段的模型變更後,包括 Claude Code 自行進行的變更,例如當您繼續工作階段時恢復模型 |
Elicitation |
當 MCP 伺服器在工具呼叫期間要求使用者輸入時 |
ElicitationResult |
在使用者回應 MCP 引出後,在回應傳送回伺服器之前 |
SessionEnd |
當工作階段終止時 |
Hook 如何解析
為了了解事件、匹配器和處理程式如何組合在一起,請考慮此 PreToolUse hook,它會阻止破壞性 shell 命令。
matcher 縮小到 Bash 工具呼叫,if 條件進一步縮小到符合 rm * 的 Bash 子命令,因此 block-rm.sh 僅在兩個篩選器都符合時才生成:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}
該指令碼從 stdin 讀取 JSON 輸入,提取命令,如果包含 rm -rf,則返回 permissionDecision 為 "deny"。將其儲存到您的專案中的 .claude/hooks/block-rm.sh,並使用 chmod +x .claude/hooks/block-rm.sh 使其可執行,以便 Claude Code 可以執行它:
#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0 # no decision; normal permission flow applies
fi
此指令碼,如同本頁面上解析 JSON 輸入的其他 Bash 範例,使用 jq,因此在嘗試之前請安裝 jq 並確保它在您的 PATH 上。
匹配器 Bash|PowerShell 涵蓋 PowerShell 工具以及 Bash。單一 if 規則只符合一個工具的呼叫,因此每個工具都有自己的處理程式:第一個縮小到符合 rm * 的 Bash 子命令,第二個縮小到符合 Remove-Item * 的 PowerShell 命令。兩者都透過 powershell.exe 執行相同的指令碼:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|PowerShell",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"
]
},
{
"type": "command",
"if": "PowerShell(Remove-Item *)",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"
]
}
]
}
]
}
}
-NoProfile 旗標會跳過載入您的 PowerShell 設定檔,以便 hook 快速啟動,而 -ExecutionPolicy Bypass 讓 PowerShell 執行本機指令碼檔案。
該指令碼從 stdin 讀取 JSON 輸入,提取命令,如果包含 rm -rf 或 Remove-Item 後跟 -Recurse,則返回 permissionDecision 為 "deny"。將其儲存到您的專案中的 .claude/hooks/block-rm.ps1:
# .claude/hooks/block-rm.ps1
$callInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
$command = $callInput.tool_input.command
if ($command -match 'rm -rf|Remove-Item.*-Recurse') {
@{
hookSpecificOutput = @{
hookEventName = "PreToolUse"
permissionDecision = "deny"
permissionDecisionReason = "Destructive command blocked by hook"
}
} | ConvertTo-Json
} else {
exit 0 # no decision; normal permission flow applies
}
現在假設 Claude Code 決定針對 macOS/Linux 設定執行 Bash "rm -rf /tmp/build"。以下是發生的情況:
事件觸發
PreToolUse 事件觸發。Claude Code 將工具輸入作為 JSON 在 stdin 上發送到 hook:
{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
匹配器檢查
匹配器 "Bash" 符合工具名稱,因此此 hook 群組啟動。如果您省略匹配器或使用 "*",群組在事件的每次出現時啟動。
If 條件檢查
if 條件 "Bash(rm *)" 符合,因為 rm -rf /tmp/build 是符合 rm * 的子命令,因此此處理程式生成。如果命令是 npm test,if 檢查會失敗,block-rm.sh 永遠不會執行,避免程序生成開銷。if 欄位是可選的;沒有它,符合群組中的每個處理程式都執行。
Hook 處理程式執行
該指令碼檢查完整命令並找到 rm -rf,因此它將決定列印到 stdout:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}
如果命令是更安全的 rm 變體,如 rm file.txt,指令碼會改為執行 exit 0。Exit code 0 且沒有輸出表示 hook 沒有決定要報告,因此工具呼叫會繼續通過正常的權限流程。Hook 可以拒絕呼叫,但保持沉默不會批准它。
Claude Code 根據結果採取行動
Claude Code 讀取 JSON 決定,阻止工具呼叫,並向 Claude 顯示原因。
下面的設定部分記錄了完整架構,每個 hook 事件部分記錄了您的命令接收的輸入以及它可以返回的輸出。
設定
Hook 定義在 JSON 設定檔中。設定有三層巢狀結構:
- 選擇要回應的 hook 事件,例如
PreToolUse或Stop - 新增 matcher 群組來篩選觸發時機,例如「僅限 Bash 工具」
- 定義一個或多個在符合時執行的 hook 處理常式
請參閱上方的 Hook 如何解析,其中有附註解範例的完整逐步說明。
本頁針對每個層級使用特定術語:hook 事件指生命週期中的時間點,matcher 群組指篩選條件,hook 處理常式指實際執行的 shell 命令、HTTP 端點、MCP 工具、提示詞或 agent。單獨使用「hook」時指的是整體功能。
Hook 位置
定義 hook 的位置決定其範圍:
| 位置 | 範圍 | 可共用 |
|---|---|---|
~/.claude/settings.json |
您的所有專案 | 否,僅限您的電腦本機 |
.claude/settings.json |
單一專案 | 是,可提交至儲存庫 |
.claude/settings.local.json |
單一專案 | 否,Claude Code 將設定儲存至此檔時會將其加入 gitignore |
| 受管政策設定 | 整個組織 | 是,由管理員控管 |
外掛 hooks/hooks.json |
外掛啟用時 | 是,與外掛綁定 |
| Skill frontmatter | 叫用 skill 後的工作階段剩餘期間。請參閱 skill 與 agent 中的 hook | 是,定義於 skill 檔案中 |
| Subagent frontmatter | 該 subagent 執行期間 | 是,定義於 subagent 檔案中 |
雲端工作階段不會讀取您本機的 ~/.claude/settings.json。在自行託管環境中,Claude Code 也會執行操作者從 runner 主機的 ~/.claude/ 預先植入的 hook;當 runner 映像檔的受管設定檔屬於 Claude Code 套用的受管來源之一時,Claude Code 也會執行該檔案中的 hook,而依預設,這僅在伺服器受管設定與透過 MDM 傳遞的 Claude Code 政策都未提供受管層級時才會發生。請參閱從您的設定中沿用的項目,了解哪些設定檔與外掛(以及因此哪些 hook)會進入雲端工作階段。
如需設定檔解析的詳細資訊,請參閱設定。
來自設定檔、受管政策設定與外掛的 hook 也會在 subagent 內執行。當 subagent 呼叫工具時,PreToolUse 與 PostToolUse 等工具事件會觸發與主對話中相同的已設定 hook,且輸入會帶有用於識別 subagent 的 agent_id 與 agent_type 通用輸入欄位。
管理員可以在受管設定中使用 allowManagedHooksOnly 來限制可執行的 hook:
- 您的使用者、專案、本機及外掛 hook 會被封鎖。在受管設定
enabledPlugins中強制啟用的外掛所提供的 hook 不受此限 - Claude Code 也會將您的
statusLine、fileSuggestion與subagentStatusLine設定限縮為僅採用受管設定 - 除非
disableCommandPluginSources明確設為false,否則 Claude Code 也會停用具有command來源的外掛,包括在受管設定enabledPlugins中強制啟用的外掛。command來源需要 Claude Code v2.1.229 或更新版本 - 除非
disableCommandPluginSources明確設為false,否則 Claude Code 也會封鎖市集的headersHelper命令,但受管設定本身宣告的市集除外
請參閱在 allowManagedHooksOnly 下會執行的項目。
Hook 項目會在各設定層級之間合併,而不是互相取代:使用者、專案與本機設定會加入各自的 hook,而不會移除受管 hook;且在受管設定以外設定的 disableAllHooks 無法停用受管 hook。
HTTP hook 允許清單適用於所有來源的 hook,包括受管政策設定:
allowedHttpHookUrls:只要在任一設定層級中定義,Claude Code 僅會在 HTTP hook 處理常式的 URL 符合合併後的允許清單時執行它httpHookAllowedEnvVars:定義後,Claude Code 只會將清單上的環境變數插入 hook 標頭中
Matcher 模式
matcher 欄位用於篩選 hook 的觸發時機。Matcher 的評估方式取決於其包含的字元:
| Matcher 值 | 評估方式 | 範例 |
|---|---|---|
"*"、"" 或省略 |
全部符合 | 每次發生該事件時都會觸發 |
僅包含字母、數字、_、-、空格、, 與 | |
精確字串,或以 | 或 , 分隔的精確字串清單,前後可有空白 |
Bash 僅符合 Bash 工具;Edit|Write 與 Edit, Write 皆精確符合這兩個工具中的任一個;code-reviewer 僅符合該 agent 類型 |
| 包含任何其他字元 | JavaScript 正規表示式,未錨定 | ^Notebook 符合名稱以 Notebook 開頭的任何工具;mcp__memory__.* 符合來自 memory 伺服器的所有工具 |
走正規表示式路徑的 matcher 會以 JavaScript 的 RegExp.prototype.test 進行測試,只要值中任何位置有符合即成功。Edit.* 同時符合 Edit 與 NotebookEdit;需要完整字串比對時,請以 ^ 與 $ 包住模式,例如 ^Edit$。
FileChanged 與 StopFailure 使用較窄的精確比對字元集,僅限字母、數字、_ 與 |。這兩個事件的 matcher 中若含有連字號、空格或逗號,就會走正規表示式路徑,且只有 | 能分隔替代項目。下表中其他支援 matcher 的事件皆接受 | 或 ,。
FileChanged 事件在建立監看清單時不遵循這些規則。請參閱 FileChanged。
每種事件類型比對的欄位不同:
| 事件 | Matcher 篩選的對象 | Matcher 值範例 |
|---|---|---|
PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest、PermissionDenied |
工具名稱 | Bash、Edit|Write、mcp__.* |
SessionStart |
工作階段的啟動方式 | startup、resume、clear、compact、fork |
Setup |
觸發 setup 的 CLI 旗標 | init、maintenance |
SessionEnd |
工作階段結束的原因 | clear、resume、logout、prompt_input_exit、other |
Notification |
通知類型 | permission_prompt、idle_prompt、auth_success、elicitation_dialog、elicitation_url_dialog、elicitation_complete、elicitation_response、agent_needs_input、agent_completed、quota_auto_resume_fired、quota_auto_resume_stale、quota_auto_resume_disabled |
SubagentStart |
agent 類型 | general-purpose、Explore、Plan、自訂 agent 名稱,或外掛範圍的名稱,例如 ^my-plugin:reviewer$ |
PreCompact、PostCompact |
觸發壓縮的原因 | manual、auto |
PreModelSwitch、PostModelSwitch |
工作階段要切換到的模型之標準名稱,如 PreModelSwitch 中所述 | claude-opus-5、claude-opus-4-6|claude-opus-5、.*opus.* |
SubagentStop |
agent 類型 | 與 SubagentStart 相同的值 |
ConfigChange |
設定來源 | user_settings、project_settings、local_settings、policy_settings、skills |
CwdChanged |
不支援 matcher | 每次發生時一律觸發 |
DirectoryAdded |
目錄的加入方式 | slash_command、register_repo_root |
FileChanged |
要監看的字面檔名(請參閱 FileChanged) | .envrc|.env |
StopFailure |
錯誤類型 | rate_limit、overloaded、authentication_failed、oauth_org_not_allowed、account_on_hold、billing_error、invalid_request、model_not_found、server_error、max_output_tokens、cloud_credential_error、unknown |
InstructionsLoaded |
載入原因 | session_start、nested_traversal、path_glob_match、include、compact |
UserPromptExpansion |
命令名稱 | 您的 skill 或命令名稱 |
Elicitation |
MCP 伺服器名稱 | 您已設定的 MCP 伺服器名稱 |
ElicitationResult |
MCP 伺服器名稱 | 與 Elicitation 相同的值 |
UserPromptSubmit、PostToolBatch、Stop、TeammateIdle、TaskCreated、TaskCompleted、WorktreeCreate、WorktreeRemove、MessageDisplay |
不支援 matcher | 每次發生時一律觸發 |
以 cloud_credential_error 比對 StopFailure 需要 Claude Code v2.1.267 或更新版本,這是第一個以該值回報憑證載入失敗,而非以 server_error 或 unknown 回報的版本。
對於大多數事件,Claude Code 會以它透過 stdin 傳送給您的 hook 的 JSON 輸入中的某個欄位來評估 matcher。對於工具事件,該欄位為 tool_name。對於 PreModelSwitch 與 PostModelSwitch,Claude Code 會以從 to_model 推導出的標準名稱來評估 matcher,如 PreModelSwitch 中所述。每個 hook 事件章節都列出了該事件完整的 matcher 值與輸入 schema。
此範例僅在 Claude 寫入或編輯檔案時執行 lint 指令碼:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "/path/to/lint-check.sh"
}
]
}
]
}
}
若您為不支援 matcher 的事件加入 matcher 欄位,該欄位會被靜默忽略。
對於工具事件,您可以在個別 hook 處理常式上設定 if 欄位以進一步縮小篩選範圍。if 使用權限規則語法同時比對工具名稱與引數,因此 "Bash(git *)" 會在 Bash 輸入的任何子命令符合 git * 時執行,而 "Edit(*.ts)" 僅對 TypeScript 檔案執行。
比對 MCP 工具
MCP 伺服器的工具在工具事件(PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest、PermissionDenied)中會以一般工具的形式出現,因此您可以用比對其他工具名稱的相同方式來比對它們。
MCP 工具遵循 mcp__<server>__<tool> 命名模式,例如:
mcp__memory__create_entities:Memory 伺服器的建立實體工具mcp__filesystem__read_file:Filesystem 伺服器的讀取檔案工具mcp__github__search_repositories:GitHub 伺服器的搜尋工具
若要比對某個伺服器的所有工具,請在伺服器前綴後加上 .*。.* 是必要的:像 mcp__memory 或 mcp__brave-search 這樣的 matcher 只包含精確比對字元,因此會被當作精確字串比較,不會符合任何工具。
mcp__memory__.*符合memory伺服器的所有工具mcp__brave-search__.*符合名稱含有連字號的伺服器的所有工具mcp__.*__write.*符合任何伺服器中名稱以write開頭的任何工具
來自外掛綁定 MCP 伺服器的工具使用包含外掛名稱的範圍化伺服器區段:mcp__plugin_<plugin-name>_<server-name>__<tool>。針對單純伺服器鍵撰寫的 matcher 永遠不會對這些工具觸發。例如名為 my-plugin 的外掛以鍵 db 綁定了一個伺服器,則 query 工具會顯示為 mcp__plugin_my-plugin_db__query,因此比對該伺服器所有工具的 matcher 為 mcp__plugin_my-plugin_db__.*。在處理常式的 if 欄位中也請使用相同的範圍化工具名稱。請參閱外掛提供的 MCP 伺服器,了解範圍化名稱的組成方式。
此範例會記錄所有 memory 伺服器的操作,並驗證來自任何 MCP 伺服器的寫入操作:
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__memory__.*",
"hooks": [
{
"type": "command",
"command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
}
]
},
{
"matcher": "mcp__.*__write.*",
"hooks": [
{
"type": "command",
"command": "/home/user/scripts/validate-mcp-write.py"
}
]
}
]
}
}
Hook 處理常式欄位
內層 hooks 陣列中的每個物件都是一個 hook 處理常式:即 matcher 符合時執行的 shell 命令、HTTP 端點、MCP 工具、LLM 提示詞或 agent。共有五種類型:
- 命令 hook(
type: "command"):執行 shell 命令。您的指令碼會透過 stdin 接收事件的 JSON 輸入,並透過退出碼與 stdout 回傳結果。 - HTTP hook(
type: "http"):將事件的 JSON 輸入以 HTTP POST 請求傳送至某個 URL。端點會透過回應主體,以與命令 hook 相同的 JSON 輸出格式回傳結果。 - MCP 工具 hook(
type: "mcp_tool"):呼叫已設定的 MCP 伺服器上的工具。工具的文字輸出會比照命令 hook 的 stdout 處理。 - 提示詞 hook(
type: "prompt"):將提示詞傳送給 Claude 模型進行單回合評估。模型會以 JSON 回傳其決定。請參閱以提示詞為基礎的 hook。 - Agent hook(
type: "agent"):產生一個 subagent,可使用 Read、Grep 與 Glob 等工具驗證條件後再回傳決定。Agent hook 屬實驗性功能,可能會變更。請參閱以 agent 為基礎的 hook。
所有符合的 hook 會平行執行。若您在多個設定檔中定義相同的處理常式,它只會執行一次。外掛或 skill 中的相同處理常式副本則會分開執行。
處理常式會在目前目錄中以 Claude Code 的環境執行。若目前目錄已不存在,例如在工作階段中途被另一個 shell 刪除的 worktree 或暫存目錄,Claude Code 會從下列目錄中第一個仍存在者執行命令 hook:工作階段啟動時的目錄、專案根目錄、您的家目錄,或系統暫存目錄。Claude Code 會在偵錯日誌中記錄一則指出備援目錄的警告。
$CLAUDE_CODE_REMOTE 環境變數在遠端 Web 環境中為 "true",在本機 CLI 中則未設定。Claude Code v2.1.199 及更新版本會在本機工作階段具有作用中的 Remote Control 連線時,將 $CLAUDE_CODE_BRIDGE_SESSION_ID 設為 Remote Control 工作階段 ID。
通用欄位
這些欄位適用於所有 hook 類型:
| 欄位 | 必要 | 說明 |
|---|---|---|
type |
是 | "command"、"http"、"mcp_tool"、"prompt" 或 "agent" |
if |
否 | 用於篩選此 hook 執行時機的權限規則語法,例如 "Bash(git *)" 或 "Edit(*.ts)"。只有在工具呼叫符合該模式時,hook 命令才會執行。請參閱下方的 Bash 比對表,了解 Bash 模式如何針對子命令、$() 與反引號進行評估。僅在工具事件上評估:PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest 與 PermissionDenied。在其他事件上,設定了 if 的 hook 永遠不會執行。使用與權限規則相同的語法 |
timeout |
否 | 取消前的秒數。對於以 async: true 執行的命令 hook,Claude Code 不會強制執行此值。預設值:command、http 與 mcp_tool 為 600;prompt 為 30;agent 為 60。在 UserPromptSubmit、PreModelSwitch 與 PostModelSwitch 上,Claude Code 會將 command、http 與 mcp_tool 的預設值降為 30,在 MessageDisplay 上則降為 10。SessionEnd hook 共用 1.5 秒的時間預算;若您的設定為個別 hook 設定了更長的 timeout,Claude Code 會將預算提高以配合,最多 60 秒 |
statusMessage |
否 | hook 執行期間顯示的自訂轉圈訊息 |
once |
否 | 若為 true,Claude Code 會在 hook 第一次成功執行後將其移除。執行失敗、以退出碼 2 封鎖或逾時的情況下,hook 會保留,因此在下一個符合的事件上會再次執行。僅對在 skill frontmatter 中宣告的 hook 生效;在設定檔與 agent frontmatter 中會被忽略 |
if 欄位只能容納一條權限規則。沒有 &&、|| 或清單語法可用來組合規則;若要套用多個條件,請為每個條件分別定義 hook 處理常式。
在檔案工具的 if 條件中,像 "Edit(src/**)" 這樣的單一區段目錄模式只會符合工作目錄中的 src 目錄及其下的檔案。若要符合任意深度中名為 src 的目錄,請寫成 "Edit(**/src/**)"。在 v2.1.214 之前,"Edit(src/**)" 會符合工作目錄下任意深度中名為 src 的目錄。
對於 Bash 模式,您的 hook 命令是否執行取決於模式的形式以及 Claude 所叫用的 Bash 命令。比對前會先移除開頭的 VAR=value 指派。
if 模式 |
Bash 命令 | Hook 是否執行? | 原因 |
|---|---|---|---|
Bash(git *) |
FOO=bar git push |
是 | 開頭的指派會被移除;git push 符合 |
Bash(git *) |
npm test && git push |
是 | 每個子命令都會檢查;git push 符合 |
Bash(rm *) |
echo $(rm -rf /) |
是 | $() 與反引號內的命令會被檢查;rm -rf / 符合 |
Bash(rm *) |
echo $(date) |
否 | 沒有子命令符合 rm * |
Bash(git push *) |
echo $(date) |
是 | 指定超過命令名稱的模式,在遇到 $()、反引號或 $VAR 時仍會執行 hook |
當 Claude Code 無法判斷 Bash 輸入會執行哪些命令時,無論模式為何都會執行您的 hook。由於 if 篩選僅為盡力而為,若要強制實施嚴格的允許或拒絕,請使用權限系統而非 hook。
命令 hook 欄位
除了通用欄位之外,命令 hook 還接受下列欄位:
| 欄位 | 必要 | 說明 |
|---|---|---|
command |
是 | 要執行的 shell 命令。搭配 args 時,則為要直接產生的可執行檔。請參閱 Exec 形式與 shell 形式 |
args |
否 | 引數清單。若有此欄位,command 會被解析為可執行檔,並以 args 作為引數向量直接產生,不經過 shell。請參閱 Exec 形式與 shell 形式 |
async |
否 | 若為 true,會在背景執行而不封鎖。請參閱在背景執行 hook |
asyncRewake |
否 | 若為 true,會在背景執行,並在退出碼為 2 時喚醒 Claude。Hook 的 stderr(若 stderr 為空則為 stdout)會以系統提醒的形式顯示給 Claude,讓它能對長時間執行的背景失敗做出反應 |
shell |
否 | 此 hook 使用的 shell。接受 "bash" 或 "powershell"。預設為 "bash",在未安裝 Git Bash 的 Windows 上則為 "powershell"。設為 "powershell" 會在 Windows 上透過 PowerShell 執行命令。由於 hook 會直接產生 PowerShell,因此不需要 CLAUDE_CODE_USE_POWERSHELL_TOOL。設定了 args 時會被忽略 |
Exec 形式與 shell 形式
設定了 args 時,命令 hook 會以 exec 形式執行;省略 args 時則以 shell 形式執行。只要 hook 參照了路徑預留位置,就請設定 args,因為每個元素都會作為單一引數傳遞,無需加引號。當您需要管線或 && 等 shell 功能,或上述兩種情況都不適用時,請省略 args。
Exec 形式在有 args 時執行。Claude Code 會將 command 解析為 PATH 上的可執行檔,並以 args 作為引數向量直接產生它。由於沒有 shell,每個 args 元素都會完全依照所寫的內容成為一個引數,而 ${CLAUDE_PLUGIN_ROOT} 等路徑預留位置會以純字串形式代入 command 與每個 args 元素。撇號、$ 與反引號等特殊字元會原封不動地傳遞,因為沒有 shell 會解譯它們。在任何平台上都不會進行 shell 斷詞。
Shell 形式在沒有 args 時執行。command 字串會傳給 shell:macOS 與 Linux 上為 sh -c,Windows 上為 Git Bash,未安裝 Git Bash 時則為 PowerShell。設定 shell 欄位可明確選擇。Shell 會對字串進行斷詞、展開變數,並解譯管線、&&、重新導向與萬用字元。
在 Windows 上,exec 形式要求 command 解析為真正的可執行檔,例如 .exe。npm、npx、eslint 與其他工具安裝在 node_modules/.bin 中的 .cmd 與 .bat shim 並非可執行檔,無法在沒有 shell 的情況下產生。若要以 exec 形式執行它們,請直接以 node 叫用底層指令碼,例如 "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/node_modules/eslint/bin/eslint.js"]。node 加上指令碼路徑的模式在所有平台上都可運作,因為 node.exe 是真正的二進位檔。若要依名稱執行 .cmd 或 .bat shim,請使用 shell 形式。
此範例執行與外掛綁定的 Node 指令碼。Exec 形式會將解析後的指令碼路徑作為單一引數傳遞,無需加引號:
{
"type": "command",
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/format.js", "--fix"]
}
等效的 shell 形式需要加引號,以處理含有空格或特殊字元的路徑:
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/format.js --fix"
}
兩種形式都支援相同的路徑預留位置,並且都會在產生的程序上將它們匯出為環境變數 CLAUDE_PROJECT_DIR、CLAUDE_PLUGIN_ROOT 與 CLAUDE_PLUGIN_DATA,因此無論指令碼是如何啟動的,都能讀取 process.env.CLAUDE_PLUGIN_ROOT。
外掛 hook 另外還會代入 ${user_config.*} 值,但僅限 exec 形式:該值會以純字串形式代入 command 與每個 args 元素,因此不會被 shell 重新剖析。
command 參照 ${user_config.*} 的 shell 形式外掛 hook 會以錯誤失敗,而不會執行。若要在 shell 形式的 hook 中使用選項值,請讀取 $CLAUDE_PLUGIN_OPTION_<KEY> 環境變數,例如 webhook_url 選項對應的 $CLAUDE_PLUGIN_OPTION_WEBHOOK_URL,或設定 args 將 hook 切換為 exec 形式。在 v2.1.207 之前,shell 形式的外掛 hook 命令也會代入 ${user_config.*}。
在 exec 形式中,command 只能是可執行檔名稱或路徑。若 command 是不含路徑分隔符號的單純名稱,且在有 args 的情況下包含空白,Claude Code 會記錄警告,因為產生程序將會失敗:不存在名為 node script.js 的可執行檔。請將多餘的 token 移至 args。含有空格的絕對路徑,例如 C:\Program Files\nodejs\node.exe,是單一有效的可執行檔,不會觸發警告。
HTTP hook 欄位
除了通用欄位之外,HTTP hook 還接受下列欄位:
| 欄位 | 必要 | 說明 |
|---|---|---|
url |
是 | POST 請求要傳送到的 URL |
headers |
否 | 以鍵值對表示的額外 HTTP 標頭。值支援使用 $VAR_NAME 或 ${VAR_NAME} 語法插入環境變數。只有列在 allowedEnvVars 中的變數會被解析 |
allowedEnvVars |
否 | 可插入標頭值中的環境變數名稱清單。對未列出之變數的參照會被替換為空字串。任何環境變數插入都必須設定此欄位才能運作 |
Claude Code 會將 hook 的 JSON 輸入作為 POST 請求主體傳送,並帶有 Content-Type: application/json。回應主體使用與命令 hook 相同的 JSON 輸出格式。
錯誤處理方式與命令 hook 不同;請參閱 HTTP 回應處理。
此範例將 PreToolUse 事件傳送至本機驗證服務,並使用 MY_TOKEN 環境變數中的 token 進行驗證:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"timeout": 30,
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}
MCP 工具 hook 欄位
除了通用欄位之外,MCP 工具 hook 還接受下列欄位:
| 欄位 | 必要 | 說明 |
|---|---|---|
server |
是 | 已設定的 MCP 伺服器名稱。對於外掛綁定的伺服器,這是範圍化名稱 plugin:<plugin-name>:<server-name>,例如 plugin:my-plugin:db,而非單純的伺服器鍵 |
tool |
是 | 要在該伺服器上呼叫的工具名稱 |
input |
否 | 傳遞給工具的引數。字串值支援從 hook 的 JSON 輸入進行 ${path} 代入,例如 "${tool_input.file_path}" |
此範例會在每次 Write 或 Edit 後,呼叫 my_server MCP 伺服器上的 security_scan 工具,並傳入被編輯檔案的路徑:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "my_server",
"tool": "security_scan",
"input": { "file_path": "${tool_input.file_path}" }
}
]
}
]
}
}
如何讀取工具的結果
Claude Code 讀取工具文字內容的方式與讀取命令 hook stdout 相同,遵循退出碼 0 下的剖析規則。若工具回傳 isError: true,hook 會產生非封鎖性錯誤,並繼續執行。
當伺服器仍在連線中
在 hook 可以封鎖或變更結果的事件上,例如 PreToolUse 或 Stop,Claude Code 會在呼叫工具前等待正在連線的伺服器,最多等待 MCP_TIMEOUT,且不超過 hook 本身的 timeout。在觀察性事件上,例如 Notification 或 SessionEnd,則不會等待。
顯示 cached 狀態的伺服器會在 hook 呼叫其工具時進行連線。若此時伺服器未連線,hook 會產生非封鎖性錯誤,並繼續執行。Hook 永遠不會啟動 OAuth 流程,因此請先從 /mcp 驗證伺服器。
在 MCP 伺服器可用前觸發的事件
啟動時的 SessionStart(包括使用 --continue 或 --resume 時)以及每個 Setup 事件,都會在工作階段的 MCP 伺服器可供 hook 使用之前觸發。Claude Code 會略過這些事件的 mcp_tool hook 而不呼叫工具,且偵錯日誌會記錄 mcp_tool hooks are not available for the 'SessionStart' hook event (no MCP client context),或指名 Setup 的相同訊息。當 SessionStart 在工作階段稍後(/clear 或壓縮之後)再次觸發時,其 mcp_tool hook 會執行。對於工作階段啟動時所需的任何內容,請改在 SessionStart 上使用 type: "command" hook。
提示詞與 agent hook 欄位
除了通用欄位之外,提示詞與 agent hook 還接受下列欄位:
| 欄位 | 必要 | 說明 |
|---|---|---|
prompt |
是 | 要傳送給模型的提示詞文字。使用 $ARGUMENTS 作為 hook 輸入 JSON 的預留位置。若要包含字面文字,請以反斜線跳脫:\$1.00 會呈現為 $1.00 |
model |
否 | 用於評估的模型。預設為 Claude Code 用於背景功能的模型 |
以路徑參照指令碼
使用這些預留位置,以相對於專案或外掛根目錄的方式參照 hook 指令碼,不受 hook 執行時的工作目錄影響:
${CLAUDE_PROJECT_DIR}:工作階段啟動時的專案根目錄。Claude Code 也會在 stdio MCP 伺服器與外掛 LSP 伺服器的環境中設定此變數。${CLAUDE_PLUGIN_ROOT}:外掛的安裝目錄,用於與外掛綁定的指令碼。請參閱外掛環境變數,了解此路徑在更新時的行為。${CLAUDE_PLUGIN_DATA}:外掛的持久資料目錄,用於應在外掛更新後保留的相依性與狀態。
Worktree 的情況不同。 若 Claude 在工作階段期間進入 worktree,Claude Code 會讓 ${CLAUDE_PROJECT_DIR} 維持原樣,並以另一種方式將 worktree 路徑傳給您的 hook:
${CLAUDE_PROJECT_DIR}保持不變:它仍指向工作階段啟動時的專案根目錄,因此像${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh這樣的命令仍會在主要 checkout 中執行指令碼。cwd跟隨 Claude:Claude 進入 worktree 後,hook 輸入 JSON 中的cwd欄位為 worktree 根目錄;Claude 執行cd後則為新的目錄。當 hook 需要知道 Claude 正在哪個目錄中工作時,請讀取此欄位。
任何參照路徑預留位置的 hook,建議使用 exec 形式。在 shell 形式中,請以雙引號包住每個預留位置。
此範例使用 ${CLAUDE_PROJECT_DIR},在任何 Write 或 Edit 工具呼叫後,從專案的 .claude/hooks/ 目錄執行樣式檢查器:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh",
"args": []
}
]
}
]
}
}
在 hooks/hooks.json 中定義外掛 hook,可選擇加入頂層的 description 欄位。外掛啟用時,其 hook 會與您的使用者及專案 hook 合併。
此範例執行與外掛綁定的格式化指令碼:
{
"description": "Automatic code formatting",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
"args": [],
"timeout": 30
}
]
}
]
}
}
如需建立外掛 hook 的詳細資訊,請參閱外掛元件參考。
Skill 與 agent 中的 hook
除了設定檔與外掛之外,hook 也可以使用 frontmatter 直接定義在 skill 與 subagent 中,其設定格式與以設定為基礎的 hook 相同。Claude Code 保留其註冊的時間長短取決於元件:
- Subagent hook:Claude Code 只會在該 subagent 執行期間執行它們,並在其完成時移除。Claude Code 會將此處的
Stophook 轉換為SubagentStop,也就是 subagent 完成時觸發的事件。 - Skill hook:Claude Code 會在您或 Claude 叫用 skill 時註冊它們,並在工作階段剩餘期間持續執行,包括 skill 本身所在回合之後的回合。若要讓 Claude Code 改為在 hook 第一次成功執行後將其移除,請在其上設定
once: true。
此 skill 定義了一個 PreToolUse hook,會在每個 Bash 命令之前執行安全驗證指令碼:
---
name: secure-operations
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
---
Subagent 在其 YAML frontmatter 中使用相同的格式。
專案 skill 中的 frontmatter hook 遵循與設定檔中的 hook 相同的工作區信任規則。Claude Code 會在您或 Claude 叫用 skill 時註冊它們,包括在您尚未信任的資料夾中執行 -p 時。
專案 subagent 中的 frontmatter hook,只有在您為 agent 檔案所在的資料夾接受工作區信任對話方塊後才會執行。-p 工作階段不算作接受。信任資料夾前會執行的項目將此與設定檔規則進行比較,而 subagent 頁面列出了哪些範圍不受此限。在 v2.1.218 之前,這些 hook 可能會從您尚未信任的資料夾執行。
`/hooks` 選單
在 Claude Code 中輸入 /hooks,即可開啟已設定 hook 的唯讀瀏覽器。清單會標示每個 hook 的來源,例如使用者設定、專案設定、本機設定、外掛或目前的工作階段。
選取某個 hook 即可查看其執行內容的完整文字以及定義位置,例如其設定檔的路徑或其外掛的名稱。
若要瀏覽所有 hook 事件,包括未設定任何 hook 的事件,請在清單末端選取 All events。
停用或移除 hook
若要移除定義在設定檔中的 hook,請從該檔案中刪除其項目。
若要暫時停用所有 hook 而不移除它們,請在您的設定檔中設定 "disableAllHooks": true。Claude Code 會讀取套用設定優先順序後剩下的值,因此專案 .claude/settings.json 中的 "disableAllHooks": false 會覆寫您使用者設定中的 true。若要不論專案設定為何都在單次執行中關閉 hook,請傳入 --settings '{"disableAllHooks": true}',它會優先於專案與本機設定。無法在保留於設定中的同時停用個別 hook。
disableAllHooks 設定會遵循受管設定的階層。若管理員已透過受管政策設定來設定 hook,則在使用者、專案或本機設定中設定的 disableAllHooks 無法停用這些受管 hook。只有在受管設定層級設定的 disableAllHooks 才能停用受管 hook。如需各層級的完整作用範圍,請參閱 disableAllHooks。
直接編輯設定檔中的 hook,通常會由檔案監看程式自動偵測並套用。
Hook 輸入和輸出
命令 hooks 通過 stdin 接收 JSON 資料,並通過退出代碼、stdout 和 stderr 傳回結果。HTTP hooks 接收相同的 JSON 作為 POST 請求正文,並通過 HTTP 回應正文傳回結果。本部分涵蓋所有事件通用的欄位和行為。每個事件在 Hook 事件 下的部分包括其特定的輸入架構和決定控制選項。
在 macOS 和 Linux 上,命令 hooks 在沒有控制終端的自己的工作階段中執行。Hook 程序和任何子程序無法開啟 /dev/tty 或直接向 Claude Code 介面發送逃逸序列。Windows 沒有 /dev/tty。
要在任何平台上向使用者顯示訊息,請在 JSON 輸出中返回 systemMessage。某些事件會捨棄它或將其傳遞到其他地方,每個 事件的部分 都會說明。要觸發桌面通知、設定視窗標題或響鈴,請改為返回 terminalSequence。
通用輸入欄位
Hook 事件接收這些欄位作為 JSON,除了每個 hook 事件 部分中記錄的事件特定欄位。對於命令 hooks,此 JSON 通過 stdin 到達。對於 HTTP hooks,它作為 POST 請求正文到達。
| 欄位 | 描述 |
|---|---|
session_id |
目前工作階段識別碼 |
prompt_id |
UUID 識別目前正在處理的使用者提示。與 OpenTelemetry 事件上的 prompt.id 屬性 相符,因此您可以將 hook 輸出與單一提示的遙測相關聯。在第一個使用者輸入之前不存在。需要 Claude Code v2.1.196 或更新版本 |
transcript_path |
對話 JSON 的路徑。成績單檔案以非同步方式寫入,可能滯後於記憶體中的對話,因此當 hook 觸發時,它可能尚未包含目前回合的最新訊息。需要目前回合最後助手文字的 Hooks 應在 Stop 和 SubagentStop 上使用 last_assistant_message,而不是讀取成績單 |
cwd |
叫用 hook 時的目前工作目錄 |
scratchpad_dir |
工作階段的 暫存目錄 的路徑,Claude 在其中保存臨時工作檔案。當工作階段沒有暫存或臨時目錄不可用時不存在。需要 Claude Code v2.1.257 或更新版本 |
permission_mode |
目前 權限模式:"default"、"plan"、"acceptEdits"、"auto"、"dontAsk" 或 "bypassPermissions"。標記為手動的模式以 "default" 到達,永遠不會以 "manual" 到達,因此匹配 "default" 的指令碼繼續工作。並非所有事件都接收此欄位。檢查每個 hook 事件 部分中的 JSON 範例 |
effort |
物件,其 level 欄位保存執行 hook 時生效的 努力等級:"low"、"medium"、"high"、"xhigh" 或 "max"。如果您設定的等級是活躍模型不支援的,level 會報告 Claude Code 實際執行的等級;調整努力等級 說明它如何選擇該等級。該物件與 狀態行 effort 欄位相符。存在於在工具使用上下文中觸發的事件,例如 PreToolUse、PostToolUse、Stop 和 SubagentStop,當目前模型支援努力參數時。該等級也可作為 $CLAUDE_EFFORT 環境變數提供給 hook 命令和 Bash 工具。 |
hook_event_name |
觸發的事件名稱 |
使用 --agent 執行或在 subagent 內執行時,包括兩個額外欄位:
| 欄位 | 描述 |
|---|---|
agent_id |
Subagent 的唯一識別碼。僅當 hook 在 subagent 呼叫內觸發時出現。使用此項來區分 subagent hook 呼叫與主執行緒呼叫。 |
agent_type |
代理名稱(例如 "Explore" 或 "security-reviewer")。當工作階段使用 --agent 或 hook 在 subagent 內觸發時出現。對於 subagents,subagent 的類型優先於工作階段的 --agent 值。請參閱 SubagentStart 以了解自訂和 plugin subagents 報告的值,以及如何針對 plugin 範圍名稱編寫匹配器。 |
只有 SessionStart hooks 可以接收 model 欄位,且 Claude Code 不一定包含它。PreModelSwitch 和 PostModelSwitch hooks 接收 from_model 和 to_model 代替,因此使用 PostModelSwitch hook 來追蹤模型在工作階段期間的變化。
沒有 $CLAUDE_MODEL 環境變數。如果您在 shell 中設定它,hook 可以讀取 $ANTHROPIC_MODEL,但當您在工作階段期間使用 /model 切換模型時,該值不會改變。
Hook 程序繼承父環境,除了 Claude Code 從它產生的每個子程序中移除 的 OTEL_* 匯出器變數,以及當 CLAUDE_CODE_SUBPROCESS_ENV_SCRUB 設定為 1 時,它剝離的變數。
例如,Bash 命令的 PreToolUse hook 在 stdin 上接收此內容:
{
"session_id": "abc123",
"prompt_id": "550e8400-e29b-41d4-a716-446655440000",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"scratchpad_dir": "/tmp/claude-1000/-home-user-my-project/abc123/scratchpad",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "Run test suite",
"timeout": 120000,
"run_in_background": false
},
"tool_use_id": "toolu_01ABC123..."
}
tool_name、tool_input 和 tool_use_id 欄位是事件特定的。每個 hook 事件 部分記錄了該事件的額外欄位。
退出代碼輸出
來自您的 hook 命令的退出代碼告訴 Claude Code 該操作是應該進行、被阻止還是被忽略。退出代碼不單獨起作用。Claude Code 在每個退出代碼上從 stdout 讀取 JSON 輸出欄位,而不僅僅是 0,對於使用標準決定模型的事件,通過架構驗證的已解析物件與代碼一起生效。Exit 2 的阻止是 JSON 無法覆蓋的唯一結果。
兩個表格擁有每個事件的例外:每個事件的退出代碼 2 行為 說明每個事件的退出代碼做什麼,決定控制 說明每個事件接受哪些決定欄位。通用欄位(如 systemMessage)在大多數事件中工作,並列在 JSON 輸出 表格中。
退出代碼 0
退出 0 表示成功,是當您列印 JSON 進行結構化控制時的預期退出代碼。
對於大多數事件,Claude Code 將 stdout 寫入詳細日誌,不在成績單中顯示。例外是 UserPromptSubmit、UserPromptExpansion、SessionStart 和 PostModelSwitch,其中 Claude Code 將純文字 stdout 新增為 Claude 可以看到和作用的上下文。
Claude Code 是否將您的 stdout 讀取為 JSON 輸出 或純文字取決於它如何開始和結束,忽略周圍的空白:
- 以
{開始並以}結束:Claude Code 將其解析為 JSON。當輸出是兩行或更多行,每行本身都解析為 JSON,且沒有行是設定欄位的 JSON 輸出 物件時,Claude Code 將整個輸出視為純文字。當其中一行確實設定欄位時,整個輸出是解析失敗,如下所述。 - 以
{開始但不以}結束:Claude Code 將其視為純文字。 - 以其他任何內容開始:Claude Code 將其視為純文字、JSON 陣列或包含的引用 JSON 字串。
對於使用標準決定模型的事件,以已解析物件退出 0 但未通過架構驗證是非阻止性錯誤:操作進行,成績單顯示 <hook name> hook error 通知,帶有驗證訊息。在任何退出代碼上都會發生相同情況,除了 2,而 exit 2 仍然阻止。
對於使用標準決定模型的事件,當 Claude Code 嘗試將您的 stdout 解析為 JSON 且無法時,它在除 2 以外的每個退出代碼上報告非阻止性錯誤。成績單顯示 <hook name> hook error 通知,帶有解析訊息。在新增純文字 stdout 作為上下文的事件上,Claude Code 不新增文字。在 v2.1.248 之前,Claude Code 將該 stdout 視為純文字。
來自以 0 退出的 hook 的 Stderr 僅進入詳細日誌,永遠不進入成績單,Claude 永遠看不到它。要自己讀取它,請啟用 詳細日誌。要從 PostToolUse 或 PostToolUseFailure hook 向 Claude 顯示警告,請改為退出 2,以便 Claude 看到 stderr,儘管工具已執行。
退出代碼 2
退出 2 表示阻止性錯誤。在 可以阻止的事件 上,退出 2 無論您是否列印 JSON 都會阻止:即使 JSON permissionDecision 為 "allow" 也無法覆蓋它。Claude Code 仍然在 stdout 上讀取任何有效的 JSON 輸出。在 Elicitation 和 ElicitationResult 上,exit-2 hook 的 hookSpecificOutput 被忽略。
阻止訊息是您的 JSON 的阻止決定的原因(當它做出決定時),否則是您的 stderr 文字。阻止做什麼因事件而異:PreToolUse 阻止工具呼叫,UserPromptSubmit 拒絕提示,等等。每個事件的退出代碼 2 行為 列出每個事件的效果,每個事件的部分說明訊息去哪裡。
在列印未通過 JSON 輸出 架構驗證的 JSON 時退出 2 的 hook 仍然阻止:Claude Code 使用 stderr 作為阻止原因,並在詳細日誌中記錄驗證失敗。在 v2.1.214 之前,Claude Code 將該組合視為非阻止性錯誤,操作進行。
此指令碼通過退出 2 阻止 rm 命令,並將每個其他命令留給正常權限流程:
#!/bin/bash
# 從 stdin 讀取 JSON 輸入,檢查命令
input=$(cat)
command=$(jq -r '.tool_input.command' <<<"$input")
if [[ "$command" == rm* ]]; then
echo "Blocked: rm commands are not allowed" >&2
exit 2 # 阻止性錯誤:工具呼叫被阻止
fi
exit 0 # 無決定:正常權限流程適用
其他退出代碼
任何其他退出代碼對於大多數 hook 事件本身不會阻止。發生的情況取決於您的 stdout:
- 使用通過架構驗證的已解析物件,對於使用標準決定模型的事件,Claude Code 忽略退出代碼,JSON 單獨決定結果:
- 使用未通過架構驗證的已解析物件,對於使用標準決定模型的事件,它與 exit 0 上 相同的非阻止性錯誤:操作進行,
<hook name> hook error通知帶有驗證訊息。 - 使用 Claude Code 嘗試解析為 JSON 且無法的 stdout,Claude Code 對於使用標準決定模型的事件報告與 exit 0 上相同的非阻止性錯誤。操作進行,通知帶有解析訊息。
- 使用 Claude Code 視為純文字 的 stdout,或使用空 stdout,對於大多數 hook 事件是非阻止性錯誤:操作進行,成績單顯示
<hook name> hook error通知,後跟 stderr 的第一行,前綴為Failed with non-blocking status code:。要捕獲完整 stderr,請啟用 詳細日誌。
標準決定模型之外的事件在 每個事件表 中保持自己的行:WorktreeCreate 在任何非零退出時失敗建立,無論您的 JSON 說什麼,事件完全捨棄 hook 輸出(如 StopFailure)除了副作用欄位(如 terminalSequence)在每個退出代碼上忽略您的 JSON,除了副作用欄位(如 terminalSequence),它仍然觸發。
無法啟動的 hook 落入相同的非阻止性桶。當指令碼路徑不存在或不可執行時,shell 以代碼(如 127)退出,您看到相同的通知,帶有解釋器的訊息,例如 Failed with non-blocking status code: /bin/sh: /path/to/hook.sh: No such file or directory。對於大多數 hook 事件,操作進行。當您設定原則 hook 時,在其第一次執行時監視此通知:settings.json 中的拼寫錯誤路徑使閘門無聲地禁用。
對於大多數 hook 事件,退出代碼 2 是唯一通過代碼單獨阻止的退出代碼。沒有 stdout 上的有效 JSON,Claude Code 將退出代碼 1 視為非阻止性錯誤並繼續操作,儘管 1 是傳統的 Unix 失敗代碼。如果您的 hook 旨在強制執行原則,請使用 exit 2。Worktree 事件不同:來自 WorktreeCreate 的任何非零退出代碼都會中止 worktree 建立,來自 WorktreeRemove 的任何非零退出代碼會在目錄仍然存在後使 worktree 移除失敗。
逾時
除了您使用 async: true 執行的命令 hook,Claude Code 取消達到其 timeout 的 command、http 或 mcp_tool hook,捨棄 hook 的輸出,因此在大多數事件上,逾時的 hook 不呈現決定。
在 PreModelSwitch 上,在其逾時時取消的 hook 阻止模型切換。在 PreToolUse 上,兩個 hook 系列不同:
- 逾時的
command、http或mcp_toolhook 不阻止工具呼叫。呼叫通過正常 權限流程 繼續,因此不要指望停滯的 hook 充當閘門。 - 超過其逾時的 Agent SDK 回調 hook 阻止工具呼叫。
每個事件的退出代碼 2 行為
退出代碼 2 是 hook 發出「停止,不要這樣做」的方式。效果取決於事件,因為某些事件代表可以被阻止的操作(例如尚未發生的工具呼叫),而其他事件代表已經發生或無法防止的事情。
| Hook 事件 | 可以阻止? | 退出 2 時發生的情況 |
|---|---|---|
PreToolUse |
是 | 阻止工具呼叫 |
PermissionRequest |
否 | 此事件不接受退出代碼 2,權限流程保持不變。改為通過 decision 物件 拒絕 |
UserPromptSubmit |
是 | 阻止提示,所以它永遠不會到達 Claude。請參閱 被阻止的提示留下什麼 |
UserPromptExpansion |
是 | 阻止擴展 |
Stop |
是 | 防止 Claude 停止,繼續對話 |
SubagentStop |
是 | 防止 subagent 停止 |
TeammateIdle |
是 | 防止隊友閒置,所以它繼續工作 |
TaskCreated |
是 | 回滾任務建立 |
TaskCompleted |
是 | 防止任務被標記為已完成 |
ConfigChange |
是 | 阻止配置變更生效(除了 policy_settings) |
StopFailure |
否 | 輸出和退出代碼被忽略,除了 terminalSequence |
PostToolUse |
否 | 向 Claude 顯示 stderr;工具已執行 |
PostToolUseFailure |
否 | 向 Claude 顯示 stderr;工具已失敗 |
PostToolBatch |
是 | 在下一個模型呼叫之前停止代理迴圈 |
PermissionDenied |
否 | 退出代碼和 stderr 被忽略,因為拒絕已發生。使用 JSON hookSpecificOutput.retry: true 告訴模型它可能重試;Claude Code 忽略 no-verdict denials 的 retry: true |
Notification |
否 | 退出代碼和 stderr 被忽略 |
SubagentStart |
否 | 僅向使用者顯示 stderr |
SessionStart |
否 | 僅向使用者顯示 stderr |
Setup |
否 | 退出代碼和 stderr 被忽略 |
SessionEnd |
否 | 僅向使用者顯示 stderr |
CwdChanged |
否 | 僅向使用者顯示 stderr |
DirectoryAdded |
否 | Stderr 進入詳細日誌;目錄已新增 |
FileChanged |
否 | 僅向使用者顯示 stderr |
PreCompact |
是 | 阻止壓縮 |
PostCompact |
否 | 僅向使用者顯示 stderr |
PreModelSwitch |
是 | 阻止模型切換並向使用者顯示 stderr |
PostModelSwitch |
否 | 僅向使用者顯示 stderr;模型已切換 |
Elicitation |
是 | 拒絕徵詢 |
ElicitationResult |
是 | 阻止回應(操作變為拒絕) |
WorktreeCreate |
是 | 任何非零退出代碼都會導致 worktree 建立失敗 |
WorktreeRemove |
是 | 任何非零退出代碼會在目錄仍然存在後使 worktree 移除失敗。請參閱 WorktreeRemove 以了解目錄發生的情況 |
InstructionsLoaded |
否 | 退出代碼被忽略 |
MessageDisplay |
否 | 原始文字被顯示 |
對於 SessionStart、SubagentStart 和 PostModelSwitch,Claude Code 在成績單中呈現退出代碼 2 stderr 作為 <hook name> hook error 通知,與 非阻止性錯誤 相同的方式。Claude 看不到它,工作階段或 subagent 繼續進行。對於 SubagentStart,通知出現在 subagent 自己的成績單中,而不是在父對話中。
HTTP 回應處理
HTTP hooks 使用 HTTP 狀態代碼和回應正文,而不是退出代碼和 stdout。下面的結果適用於大多數事件;在 每個事件表 中有自己的失敗合約的事件(如 WorktreeCreate)將該合約應用於失敗的 HTTP hook:
- 2xx 且正文為空:成功,等同於退出代碼 0 且無輸出
- 2xx 且 JSON 物件正文:使用與命令 hooks 相同的 JSON 輸出 架構進行解析。未通過架構驗證的正文是非阻止性錯誤
- 2xx 且任何其他正文,如純文字:非阻止性錯誤,處理方式與非 2xx 狀態相同。Claude Code 不將文字新增到 Claude 的上下文
- 非 2xx 狀態:非阻止性錯誤,執行繼續
- 連線失敗:非阻止性錯誤,執行繼續
- 逾時:hook 被取消,如 逾時 下所述
與命令 hooks 不同,HTTP hooks 無法僅通過狀態代碼發出阻止性錯誤信號。要阻止工具呼叫或拒絕權限,請返回 2xx 回應,其 JSON 正文包含適當的決定欄位。
JSON 輸出
退出代碼只讓您阻止或保持沉默,但 JSON 輸出提供更細粒度的控制。與其以代碼 2 退出來阻止,不如退出 0 並將 JSON 物件列印到 stdout。Claude Code 從該 JSON 讀取特定欄位以控制行為,包括 決定控制 以阻止、允許或升級給使用者。
為每個 hook 選擇一種方法:要麼單獨使用退出代碼進行信號傳遞,要麼退出 0 並列印 JSON 進行結構化控制。如果您混合它們,退出 2 保持其 阻止效果,Claude Code 仍然讀取 JSON 欄位,除了 Exit code 2 下記錄的一個徵詢例外。
您的 hook 的 stdout 必須僅包含 JSON 物件。如果您的 shell 設定檔在啟動時列印文字,它可能會干擾 JSON 解析。請參閱故障排除指南中的 Hook JSON 無效。
Hook 的 additionalContext、systemMessage 和 initialUserMessage 字串,以及其純 stdout,上限為 10,000 個字元:
- 範圍:Claude Code 分別測量每個字串,即使多個 hooks 為同一事件執行。對於 JSON 輸出,每個欄位分別測量;純 stdout 整體測量。
- 超過限制:Claude Code 將輸出儲存到工作階段目錄中的檔案,並將其替換為檔案路徑和最多前 2,000 個字元的預覽。大型有效 Bash 結果的處理方式相同,如 輸出限制 下所述。與該 Bash 上限不同,此上限沒有設定或環境變數來提高它。
- 讀取檔案:Claude Code 不要求 Claude 讀取檔案,因此將 Claude 必須始終看到的任何內容保持在上限內。
JSON 物件支援三種欄位:
- 通用欄位,如
continue,列在下表中。每個事件都接受它們,但某些事件捨棄它們或將systemMessage傳遞到成績單以外的地方。每個事件的部分都說明。terminalSequence在這些事件上也工作,除了 發出終端通知 下列出的例外。 - 頂層
decision和reason由某些事件用來阻止或提供反饋。 hookSpecificOutput是一個嵌套物件,用於需要更豐富控制的事件。它需要一個設定為事件名稱的hookEventName欄位。
| 欄位 | 預設 | 描述 |
|---|---|---|
continue |
true |
如果為 false,Claude 在 hook 執行後完全停止處理。優先於任何事件特定的決定欄位 |
stopReason |
無 | 當 continue 為 false 時向使用者顯示的訊息。它停留在對話中,因此如果對話繼續,Claude 會看到它 |
suppressOutput |
false |
無效果:Claude Code 接受欄位但不作用。成功的 hook 的 stdout 永遠不在成績單中顯示,並在詳細日誌中記錄 |
systemMessage |
無 | 向使用者顯示的警告訊息。在 Agent SDK 和 --output-format stream-json 輸出中,它可以作為 SDKInformationalMessage 到達 |
terminalSequence |
無 | Claude Code 代表您發出的終端逃逸序列,例如桌面通知、視窗標題或響鈴。限制為 OSC 0/1/2/9/99/777 和 BEL。如果值包含允許清單外的任何內容,該欄位將被忽略。使用此項而不是寫入 /dev/tty,後者對 hooks 不可用 |
要完全停止 Claude:
{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
對於 PreToolUse 和 PostToolUse hooks,停止適用,即使工具呼叫失敗或在 Claude 仍在串流回應時完成。
發出終端通知
Hooks 在沒有控制終端的情況下執行,因此直接寫入逃逸序列到 /dev/tty 會失敗。相反,在 terminalSequence 欄位中返回逃逸序列,Claude Code 通過其自己的終端寫入路徑為您發出它。這是無競爭的,在 tmux 和 GNU screen 內工作,並在沒有 /dev/tty 的 Windows 上工作。
該欄位接受一個或多個允許清單逃逸序列的字串:
- OSC
0、1、2:視窗和圖示標題 - OSC
9:iTerm2、ConEmu、Windows Terminal 和 WezTerm 通知,包括9;4工作列進度 - OSC
99:Kitty 通知 - OSC
777:urxvt、Ghostty 和 Warp 通知 - 裸 BEL
序列可以用 BEL 或 ST 終止。允許清單外的任何內容,包括 CSI 游標和顏色序列、OSC 調色板序列、OSC 8 超連結、OSC 52 剪貼簿寫入和 OSC 1337,都會被拒絕,該欄位將被忽略。
Claude Code 在處理您的 hook 輸出時寫入序列本身,因此該欄位在捨棄 systemMessage 和 continue 的事件上工作,例如 Notification 和 StopFailure。它有兩個限制:
- Claude Code 僅在互動式工作階段中寫入序列,且僅在其介面在螢幕上時。在使用
-p旗標的非互動式模式和 Agent SDK 中,它忽略該欄位。 WorktreeCreate命令 hook 無法返回 JSON,因為 Claude Code 將其 stdout 讀取為 worktree 路徑。HTTPWorktreeCreatehook 返回 JSON 並可以包含該欄位。
下面的範例從 Notification hook 觸發桌面通知。逃逸序列使用 printf 八進位逃逸構建,因此控制位元組永遠不會出現在 shell 命令行上,jq -n --arg 構建 JSON 輸出,因此通知訊息中的引號、反斜線和換行符被正確逃逸:
#!/bin/bash
# Notification hook:當 Claude Code 需要注意時 ping 桌面。
input=$(cat)
title="Claude Code"
body=$(jq -r '.message // "Needs your attention"' <<<"$input")
seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body")
jq -nc --arg seq "$seq" '{terminalSequence: $seq}'
{ "terminalSequence": "..." } 形狀在任何 shell 或語言中都相同。
為 Claude 新增上下文
additionalContext 欄位將字串從您的 hook 傳遞到 Claude 的上下文視窗。Claude Code 將字串包裝在 系統提醒 中,並將其插入到 hook 觸發的對話點。Claude 在下一個模型請求時讀取提醒,但它不會在介面中顯示為聊天訊息。
在 hookSpecificOutput 中返回 additionalContext 以及事件名稱:
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
}
}
提醒出現的位置取決於事件:
- SessionStart 和 SubagentStart:在對話開始,在第一個提示之前
- UserPromptSubmit 和 UserPromptExpansion:與提交的提示一起
- PreToolUse、PostToolUse、PostToolUseFailure 和 PostToolBatch:在工具結果旁邊
- Stop 和 SubagentStop:在回合結束。對話繼續,以便 Claude 可以對反饋採取行動。請參閱 Stop 決定控制
- PostModelSwitch:與切換後的下一個請求一起。請參閱 PostModelSwitch 決定控制 以了解時機
當多個 hooks 為同一事件返回 additionalContext 時,Claude 接收所有值。
如果值超過 10,000 個字元,Claude Code 會將文字寫入工作階段目錄中的檔案,並將檔案路徑與最多前 2,000 個字元的預覽傳遞給 Claude。Claude 可以讀取檔案,但 Claude Code 不要求它。
使用 additionalContext 來提供 Claude 應該知道的有關您環境目前狀態或剛剛執行的操作的資訊:
- 環境狀態:目前分支、部署目標或活躍的功能旗標
- 條件專案規則:哪個測試命令適用於剛編輯的檔案,此 worktree 中哪些目錄是唯讀的
- 外部資料:分配給您的開放問題、最近的 CI 結果、從內部服務擷取的內容
對於永遠不會改變的指示,優先使用 CLAUDE.md。它無需執行指令碼即可載入,是靜態專案約定的標準位置。
將文字寫成事實陳述,而不是命令式系統指示。「部署目標是生產」或「此儲存庫使用 bun test」之類的措辭讀起來像專案資訊。框架為帶外系統命令的文字可能會觸發 Claude 的提示注入防禦,這會導致 Claude 將文字呈現給您,而不是將其視為上下文。
Claude Code 在工作階段成績單中儲存注入的文字。對於 PostToolUse 或 UserPromptSubmit 等中期事件,當您使用 --continue 或 --resume 繼續時,Claude Code 重播儲存的文字,而不是為過去的回合重新執行 hook,因此時間戳或提交 SHA 等值變得陳舊。SessionStart hooks 在使用 source 設定為 "resume" 的 --resume 時再次執行,或如果您新增了 --fork-session 則為 "fork",因此它們可以刷新其上下文。
決定控制
並非每個事件都支援阻止或通過 JSON 控制行為。支援的事件各自使用不同的欄位集來表達該決定。在編寫 hook 之前,使用此表作為快速參考:
| 事件 | 決定模式 | 關鍵欄位 |
|---|---|---|
| UserPromptSubmit、UserPromptExpansion、PostToolUse、PostToolUseFailure、PostToolBatch、Stop、SubagentStop、ConfigChange、PreCompact | 頂層 decision |
decision: "block"、reason。Stop 和 SubagentStop 也接受 hookSpecificOutput.additionalContext 用於 繼續對話的非錯誤反饋 |
| TeammateIdle、TaskCompleted | 退出代碼或 continue: false |
退出代碼 2 使用 stderr 反饋阻止操作。JSON {"continue": false, "stopReason": "..."} 也會完全停止隊友,匹配 Stop hook 行為;TaskCompleted 在 TaskUpdate 工具觸發事件時忽略它 |
| TaskCreated | 退出代碼或頂層 decision |
退出代碼 2 或 decision: "block" 取消任務 並將訊息返回給 Claude。continue: false 被忽略 |
| PreToolUse | hookSpecificOutput |
permissionDecision(allow/deny/ask/defer)、permissionDecisionReason |
| PreModelSwitch | hookSpecificOutput 或頂層 decision |
permissionDecision(allow/deny/ask)、permissionDecisionReason。decision: "block" 也 取消切換 |
| PermissionRequest | hookSpecificOutput |
decision.behavior(allow/deny) |
| PermissionDenied | hookSpecificOutput |
retry: true 告訴模型它可能重試被拒絕的工具呼叫;Claude Code 忽略 no-verdict denials 的 retry: true |
| WorktreeCreate | 路徑返回 | 命令 hook 在 stdout 上列印路徑;HTTP hook 通過 hookSpecificOutput.worktreePath 返回。Hook 失敗或缺少路徑會導致建立失敗 |
| WorktreeRemove | 退出代碼 | 任何非零退出代碼會在目錄仍然存在後使移除失敗。JSON 輸出被捨棄 |
| Elicitation | hookSpecificOutput |
action(accept/decline/cancel)、content(accept 的表單欄位值) |
| ElicitationResult | hookSpecificOutput |
action(accept/decline/cancel)、content(覆蓋表單欄位值) |
| MessageDisplay | hookSpecificOutput |
displayContent 替換螢幕上顯示的文字。僅顯示:成績單和 Claude 看到的內容保持原始 |
| SessionStart、SubagentStart、PostModelSwitch | 僅上下文 | hookSpecificOutput.additionalContext 為 Claude 新增上下文。SessionStart 也接受 initialUserMessage、watchPaths、sessionTitle 和 reloadSkills。無阻止或決定控制 |
| Setup、Notification、SessionEnd、PostCompact、InstructionsLoaded、StopFailure、CwdChanged、DirectoryAdded、FileChanged | 無 | 無決定控制。用於副作用,如記錄或清理 |
一些事件也可以重寫內容,而不僅僅是允許或阻止它:
PreToolUse:updatedInput直接在hookSpecificOutput下替換工具的引數,然後執行。請參閱 PreToolUse 決定控制 以取得完整的選項集。PermissionRequest:updatedInput在decision物件內。請參閱 PermissionRequest 決定控制 以取得完整的選項集。PostToolUse:updatedToolOutput替換工具的結果。請參閱 PostToolUse 決定控制 以取得完整的選項集。UserPromptSubmit:無法替換提示;僅在其旁邊注入additionalContext
對於編輯或轉換使用案例,在 PreToolUse 攔截出站工具輸入,在 PostToolUse 攔截入站工具結果。
以下是每種模式的實際範例:
decision 的唯一值是 "block"。要允許操作進行,請從 JSON 中省略 decision,或以 0 退出而不帶任何 JSON:
{
"decision": "block",
"reason": "Test suite must pass before proceeding"
}
使用 hookSpecificOutput 進行更豐富的控制:允許、拒絕或升級給使用者。您還可以在執行前修改工具輸入或為 Claude 注入額外上下文。有關完整的選項集,請參閱 PreToolUse 決定控制。
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Database writes are not allowed"
}
}
使用 hookSpecificOutput 代表使用者允許或拒絕權限請求。允許時,您還可以修改工具的輸入或應用權限規則,以便使用者不會再次被提示。有關完整的選項集,請參閱 PermissionRequest 決定控制。
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": {
"command": "npm run lint"
}
}
}
}
有關擴展範例,包括 Bash 命令驗證、提示篩選和自動批准指令碼,請參閱指南中的 您可以自動化的內容 和 Bash 命令驗證器參考實現。
Hook 事件
每個事件對應 Claude Code 生命週期中可執行 hook 的一個時間點。以下各節依生命週期排序:從工作階段設定,經過代理式迴圈,直到工作階段結束。每一節說明事件何時觸發、支援哪些 matcher、接收的 JSON 輸入,以及如何透過輸出控制行為。
SessionStart
在 Claude Code 啟動新工作階段或繼續現有工作階段時執行。適合用來載入開發上下文,例如現有的 issue 或程式碼庫的近期變更,或設定環境變數。若是不需要指令碼的靜態上下文,請改用 CLAUDE.md。
SessionStart 會在每個工作階段執行,因此請讓這些 hook 保持快速。僅支援 type: "command" 與 type: "mcp_tool" hook。關於 mcp_tool hook 何時執行,請參閱 MCP tool hook 欄位。
matcher 值對應工作階段的啟動方式:
| Matcher | 觸發時機 |
|---|---|
startup |
新工作階段 |
resume |
--resume、--continue 或 /resume |
clear |
/clear |
compact |
自動或手動壓縮 |
fork |
從現有工作階段分叉出的新工作階段:搭配 --resume 或 --continue 使用的 --fork-session、/fork 背景副本、/branch,或您移至背景的對話 |
在 v2.1.214 之前,分叉的工作階段回報的 source 為 "resume"。
當您啟動互動式工作階段、在啟動時以 --continue 或 --resume 繼續對話,或執行 /clear 時,SessionStart hook 會在背景執行。您可以立即開始輸入,而您繼續的對話也會直接顯示,不必等待 hook。Claude 的第一個回應仍會等待 hook 完成,讓其上下文能傳達給 Claude。
若您在工作階段內以 /resume 切換對話,切換動作則會等待 hook 完成。如果您在背景 hook 仍在執行時執行 /clear 或切換到另一個對話,它們回傳的任何內容都不會套用到該工作階段。
啟動時也適用相同的等待,包括繼續的工作階段:在 SessionStart hook 仍在執行時送出的提示詞,要等到 hook 完成後才會傳達給 Claude。
在上述任一種等待期間,按下 Esc 可將提示詞收回輸入框而不送出。hook 會繼續執行。
SessionStart 輸入
除了通用輸入欄位之外,SessionStart hook 還會接收 source,以及選擇性的 model、agent_type 與 session_title:
| 欄位 | 說明 |
|---|---|
source |
工作階段的啟動方式:新工作階段為 "startup",繼續的工作階段為 "resume",/clear 之後為 "clear",壓縮之後為 "compact",從現有工作階段分叉出的新工作階段則為 "fork" |
model |
目前使用中的模型識別碼。此欄位可能被省略,例如在 /clear 之後,或工作階段透過對話復原而還原時,因此讀取前請先檢查該欄位是否存在 |
agent_type |
agent 名稱,當您以 claude --agent <name> 啟動 Claude Code 時才會出現 |
session_title |
工作階段的自訂標題,僅在已設定時出現,例如透過 --name、/rename、hook 的 sessionTitle 輸出,或 Agent SDK 的 renameSession() 設定。輸出 sessionTitle 的 hook 可以先檢查此欄位,以避免覆寫既有的自訂標題 |
您尚未命名的工作階段仍可能有自動產生的標題。該標題並非自訂標題,不會出現在 session_title 中。
當 source 為 "resume" 或 "fork",且逐字稿中至少包含一則 Claude 的回應時,SessionStart hook 也會接收以下四個欄位。您的 hook 可以用它們在第一個請求之前回報繼續一個陳舊對話的成本,例如透過 systemMessage。這些欄位需要 Claude Code v2.1.251 或更新版本。
| 欄位 | 說明 |
|---|---|
seconds_since_last_response |
自繼續的逐字稿中最後一則回應以來經過的實際秒數 |
context_tokens |
繼續的工作階段的第一個請求作為提示詞重新傳送的 token 數 |
prompt_cache_likely_expired |
當最後一則回應早於工作階段的提示快取存留期,或後續的壓縮取代了已快取的對話時,為 true |
estimated_cache_write_usd |
在工作階段的模型上將 context_tokens 寫入提示快取的預估成本(美元),不含回應 |
此範例顯示在最後一則回應 90 分鐘後繼續的工作階段的輸入:
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SessionStart",
"source": "resume",
"model": "claude-opus-5",
"seconds_since_last_response": 5400,
"context_tokens": 182340,
"prompt_cache_likely_expired": true,
"estimated_cache_write_usd": 1.1396
}
SessionStart 決策控制
Claude Code 會將其視為純文字的 stdout 加入 Claude 的上下文。除了所有 hook 都可使用的 JSON 輸出欄位之外,您還可以回傳下列事件專屬欄位:
| 欄位 | 說明 |
|---|---|
additionalContext |
在對話開始時、第一個提示詞之前加入 Claude 上下文的字串。關於文字的傳遞方式以及應放入的內容,請參閱為 Claude 加入上下文 |
initialUserMessage |
作為工作階段第一則使用者訊息的字串。適用於使用 -p 旗標的非互動模式,即使未提供提示詞,它也會成為第一個回合。若有提供提示詞,該提示詞會作為下一個回合接續。與附加到既有回合的 additionalContext 不同,此欄位會建立回合 |
sessionTitle |
設定工作階段標題,效果與 /rename 相同。可用來依據啟動資料夾、git 分支或 worktree 名稱自動為工作階段命名。在 source 為 "startup"、"resume" 或 "fork" 時套用;在 "clear" 與 "compact" 時忽略 |
watchPaths |
在此工作階段中要監看 FileChanged 事件的絕對路徑陣列 |
reloadSkills |
布林值。為 true 時,Claude Code 會在 SessionStart hook 完成後重新掃描 skill 與命令目錄,讓 hook 安裝的 skill 能在同一個工作階段中使用,從第一個提示詞開始即可使用 |
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2",
"sessionTitle": "auth-refactor"
}
}
由於此事件的純 stdout 已會傳達給 Claude,只載入上下文的 hook 可以直接輸出到 stdout,不必建構 JSON。當您需要將上下文與其他欄位(例如 sessionTitle)結合時,請使用 JSON 形式。
當 SessionStart hook 安裝或更新 skill 時,請使用 reloadSkills。skill 探索通常在 SessionStart hook 完成之前就已執行,因此 hook 寫入 ~/.claude/skills/ 或 .claude/skills/ 的檔案,否則要到下一個工作階段才會出現。此範例會同步一個共用的 skill 儲存庫並要求重新掃描:
#!/bin/bash
git -C ~/.claude/skills/team-skills pull --quiet 2>/dev/null || \
git clone --quiet https://git.example.com/your-org/team-skills.git ~/.claude/skills/team-skills
echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "reloadSkills": true}}'
儲存庫 URL 只是預留位置;請將它替換為您自己的 skill 儲存庫。使用預留位置時,clone 會失敗並將 fatal: 訊息輸出到 stderr。以退出碼 0 結束的 SessionStart hook 的 stderr 僅供參考,因此 reloadSkills 請求仍會套用。
保存環境變數
SessionStart hook 可以存取 CLAUDE_ENV_FILE 環境變數,它提供一個檔案路徑,讓您可以為後續的 Bash 命令保存環境變數。
若要設定個別環境變數,請將 export 陳述式寫入 CLAUDE_ENV_FILE。請使用附加(>>)以保留其他 hook 設定的變數:
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi
exit 0
若要擷取設定命令造成的所有環境變更,請比較執行前後匯出的變數:
#!/bin/bash
ENV_BEFORE=$(export -p | sort)
# Run your setup commands that modify the environment
source ~/.nvm/nvm.sh
nvm use 20
if [ -n "$CLAUDE_ENV_FILE" ]; then
ENV_AFTER=$(export -p | sort)
comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi
exit 0
CLAUDE_ENV_FILE 可供 SessionStart、Setup、CwdChanged 與 FileChanged hook 使用。其他 hook 類型無法存取此變數。
Setup
僅在您以 --init-only 啟動 Claude Code,或在使用 -p 旗標的非互動模式中搭配 --init 或 --maintenance 啟動時觸發。一般啟動時不會觸發。可用於一次性的相依套件安裝,或您從 CI 或指令碼明確觸發的排程清理,與一般工作階段啟動分開。若是每個工作階段的初始化,請改用 SessionStart。
matcher 值對應觸發該 hook 的 CLI 旗標:
| Matcher | 觸發時機 |
|---|---|
init |
claude --init-only 或 claude -p --init |
maintenance |
claude -p --maintenance |
當您執行 claude --init-only 時,Claude Code 會執行 Setup hook 以及 matcher 為 startup 的 SessionStart hook,然後在不啟動對話的情況下結束。
當您以 -p 開始或繼續對話時,還需要提供提示詞,作為引數或透過 stdin 傳入。當 SessionStart hook 提供 initialUserMessage,或您繼續一個帶有延後工具呼叫的工作階段時,可以省略提示詞。
成功時,--init-only 不會在終端機輸出任何內容。若要確認 hook 已執行,請以 claude --debug-file <path> --init-only 啟動,將 <path> 替換為日誌檔案位置,並在日誌中檢查 Setup 與 SessionStart hook 的項目。
由於 Setup 不會在每次啟動時觸發,需要安裝相依套件的外掛無法僅依賴 Setup。實務上的做法是在首次使用時檢查相依套件,缺少時再安裝,例如由 hook 或 skill 檢查 ${CLAUDE_PLUGIN_DATA}/node_modules,若不存在則執行 npm install。關於已安裝相依套件的存放位置,請參閱持久性資料目錄。如果您透過市集發布外掛,可能不需要此做法:Claude Code 在快取外掛時會自動安裝符合條件的 Node.js 套件相依性。
Setup 輸入
除了通用輸入欄位之外,Setup hook 還會接收 trigger 欄位,其值為 "init" 或 "maintenance":
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Setup",
"trigger": "init"
}
Setup 決策控制
Setup hook 無法阻擋;無論退出碼為何,執行都會繼續。無論退出碼為何,Claude Code 都會捨棄 Setup hook 的 JSON 輸出欄位,例如 systemMessage、continue 與 hookSpecificOutput.additionalContext。使用 -p 時,Setup hook 的 stdout、stderr 與退出碼只有在您以 --output-format stream-json --verbose 啟動時,才會以 hook_response 事件的形式出現在執行輸出中。
Setup hook 可以存取 CLAUDE_ENV_FILE。寫入該檔案的變數會保存到該工作階段的後續 Bash 命令中,與 SessionStart hook 相同。只有 type: "command" hook 會在 Setup 上執行。Setup 上的 type: "mcp_tool" hook 一律會被略過,如 MCP tool hook 欄位中所述。
InstructionsLoaded
在 CLAUDE.md 或 .claude/rules/*.md 檔案載入上下文時觸發。此事件會在工作階段開始時針對預先載入的檔案觸發,之後在延遲載入檔案時再次觸發,例如當 Claude 存取包含巢狀 CLAUDE.md 的子目錄,或帶有 paths: frontmatter 的條件式規則相符時。此 hook 不支援阻擋或決策控制。它會為了可觀察性而以非同步方式執行。
當 Claude 透過 Project instructions 設定直接讀取 AGENTS.md 時,此事件不會觸發。當 CLAUDE.md 匯入您的 AGENTS.md 時,此事件會觸發,且 load_reason 與其他任何匯入的檔案一樣設為 include;當 CLAUDE.md 是指向它的符號連結時,也會以一般的 CLAUDE.md 載入觸發。
matcher 會比對 load_reason。例如,使用 "matcher": "session_start" 只針對工作階段開始時載入的檔案觸發,或使用 "matcher": "path_glob_match|nested_traversal" 只針對延遲載入觸發。
InstructionsLoaded 輸入
除了通用輸入欄位之外,InstructionsLoaded hook 還會接收以下欄位:
| 欄位 | 說明 |
|---|---|
file_path |
已載入的指令檔案的絕對路徑 |
memory_type |
檔案的範圍:"User"、"Project"、"Local" 或 "Managed" |
load_reason |
檔案載入的原因:"session_start"、"nested_traversal"、"path_glob_match"、"include" 或 "compact"。"compact" 值會在壓縮事件後重新載入指令檔案時觸發 |
globs |
檔案 paths: frontmatter 中的路徑 glob 模式(若有)。僅在 path_glob_match 載入時出現 |
trigger_file_path |
對於延遲載入,其存取觸發此次載入的檔案路徑 |
parent_file_path |
對於 include 載入,包含此檔案的上層指令檔案路徑 |
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "InstructionsLoaded",
"file_path": "/Users/my-project/CLAUDE.md",
"memory_type": "Project",
"load_reason": "session_start"
}
InstructionsLoaded 決策控制
InstructionsLoaded hook 沒有決策控制。它們無法阻擋或修改指令載入。Claude Code 會捨棄它們的 JSON 輸出欄位,例如 systemMessage 與 continue。請將此事件用於稽核日誌、合規追蹤或可觀察性。
UserPromptSubmit
在提示詞送出後、Claude 處理之前執行。這可讓您 根據提示詞或對話加入額外情境、驗證提示詞,或 封鎖特定類型的提示詞。
UserPromptSubmit hook 不只會在您輸入的提示詞上觸發。Claude Code 也會在下列情況執行它們:
- 排程任務觸發時,包括
/loop的每次迭代 - 背景 subagent 向啟動它的工作階段回報時
- 另一個工作階段傳送訊息到您的主要對話時
UserPromptSubmit hook 對於 command、http 與 mcp_tool 類型的預設逾時為 30 秒,比這些類型在其他大多數事件上的 600 秒預設值更短。由於此 hook 會在每個提示詞之前執行,並在完成前阻擋模型處理,卡住的 hook 會讓工作階段停滯。如果您的 hook 需要更多時間,請在 hook 項目中設定 timeout 欄位。
除了您以 async: true 執行的 command hook 之外,達到逾時的 UserPromptSubmit command、HTTP 或 MCP tool hook 會被取消,其輸出(包括任何 additionalContext)都會被捨棄。提示詞仍會傳達給 Claude,只是不含該上下文。逐字稿會顯示一則通知,指出該 hook 名稱、觸發的逾時,以及輸出已被捨棄。
UserPromptSubmit 上達到逾時的 Agent SDK 回呼 hook 會以指出 hook 名稱與逾時的訊息阻擋提示詞,因為該處的回呼可能充當不得在失敗時放行的政策關卡。工作階段會繼續。在 v2.1.208 之前,該事件上的回呼逾時會以執行錯誤結束該回合。
UserPromptSubmit 輸入
除了通用輸入欄位之外,UserPromptSubmit hook 還會接收包含所送出文字的 prompt 欄位。摺疊為 [Pasted text #N] 預留位置的貼上內容,會在原位置展開後送達。在 Claude Code 為 Claude 標記貼上文字的工作階段中,展開的內容位於 <pasted_content id="…"> 行與 </pasted_content id="…"> 行之間,因此如果您的 hook 會剖析提示詞,請將這些行納入考量。
當工作階段具有自訂標題時,UserPromptSubmit hook 也會接收 session_title,其意義與 SessionStart 的 session_title 欄位相同。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "UserPromptSubmit",
"prompt": "Write a function to calculate the factorial of a number"
}
UserPromptSubmit 決策控制
UserPromptSubmit hook 可以控制是否處理已送出的提示詞,並可加入情境。所有 JSON 輸出欄位皆可使用。
在退出碼 0 時,有兩種方式可以將上下文加入對話:
- 純文字 stdout:Claude Code 會將其視為純文字的 stdout 加入 Claude 的上下文
- 帶有
additionalContext的 JSON:使用下方的 JSON 格式以獲得更多控制。additionalContext欄位會作為上下文加入
兩種管道都不會產生可見的逐字稿項目。純 stdout 與 additionalContext 值會各自以開頭為 hook 名稱的系統提醒注入;Claude 兩者都會讀取。若要確認已傳遞,請檢查偵錯日誌。
若要阻擋提示詞,請回傳 decision 設為 "block" 的 JSON 物件:
| 欄位 | 說明 |
|---|---|
decision |
"block" 會在提示詞傳達給 Claude 之前將其停止。省略則允許提示詞繼續 |
reason |
當 decision 為 "block" 時向使用者顯示。不會加入上下文 |
additionalContext |
與送出的提示詞一同加入 Claude 上下文的字串。請參閱為 Claude 加入上下文 |
sessionTitle |
設定工作階段標題。可用來依據提示詞內容自動為工作階段命名 |
suppressOriginalPrompt |
若在 hook 阻擋提示詞時為 true,則會從阻擋訊息中省略提示詞文字。請參閱被阻擋的提示詞會留下什麼 |
以退出碼 2 阻擋的 hook 與 reason 的處理方式相同:阻擋訊息會向使用者顯示 stderr 文字,且不會加入上下文。
{
"decision": "block",
"reason": "Explanation for decision",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "My additional context here",
"sessionTitle": "My session title",
"suppressOriginalPrompt": true
}
}
被阻擋的提示詞會留下什麼
被阻擋的提示詞永遠不會傳達給 Claude,但其文字並不會在所有地方被移除。根據預設,向使用者顯示的阻擋訊息結尾會是 Original prompt: 加上送出的文字,而 Claude Code 會將該訊息寫入磁碟上工作階段的逐字稿檔案。若要從訊息中省略文字,請在 hookSpecificOutput 內輸出帶有 "suppressOriginalPrompt": true 的 JSON。無論 hook 是以 decision: "block" 還是以退出碼 2 阻擋,此做法都有效。未輸出 JSON 的退出碼 2 hook,其阻擋訊息一律會包含提示詞文字。
suppressOriginalPrompt 只會變更阻擋訊息。送出的文字仍可能出現在本機檔案中,例如工作階段逐字稿與您的提示詞歷史記錄,因此阻擋 hook 並不是讓機密不落入磁碟的方法。若要限制或移除這些檔案,請參閱純文字儲存與清除本機資料。
UserPromptExpansion
在使用者輸入的命令展開為提示詞、傳達給 Claude 之前執行。可用來阻擋特定命令被直接呼叫、為特定 skill 注入上下文,或記錄使用者呼叫了哪些命令。例如,比對 deploy 的 hook 可以在核准檔案不存在時阻擋 /deploy,或比對審查 skill 的 hook 可以將團隊的審查檢查清單作為 additionalContext 附加。
此事件涵蓋 PreToolUse 未涵蓋的路徑:比對 Skill 工具的 PreToolUse hook 只會在 Claude 呼叫該工具時觸發,但直接輸入 /skillname 會繞過 PreToolUse。UserPromptExpansion 會在該直接路徑上觸發。
比對 command_name。將 matcher 留空即可在每個提示詞類型的命令上觸發。
UserPromptExpansion 輸入
除了通用輸入欄位之外,UserPromptExpansion hook 還會接收 expansion_type、command_name、command_args、command_source,以及原始的 prompt 字串。expansion_type 欄位對於 skill 與自訂命令為 slash_command,對於 MCP 伺服器提示詞則為 mcp_prompt。
{
"session_id": "abc123",
"transcript_path": "/Users/.../00893aaf.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "UserPromptExpansion",
"expansion_type": "slash_command",
"command_name": "example-skill",
"command_args": "arg1 arg2",
"command_source": "plugin",
"prompt": "/example-skill arg1 arg2"
}
UserPromptExpansion 決策控制
UserPromptExpansion hook 可以阻擋展開或加入上下文。所有 JSON 輸出欄位都可使用。
| 欄位 | 說明 |
|---|---|
decision |
"block" 會阻止命令展開。省略則允許其繼續 |
reason |
當 decision 為 "block" 時向使用者顯示 |
additionalContext |
與展開後的提示詞一同加入 Claude 上下文的字串。請參閱為 Claude 加入上下文 |
以退出碼 2 阻擋的 hook 與 reason 的處理方式相同:阻擋訊息會向使用者顯示 stderr 文字。
{
"decision": "block",
"reason": "This slash command is not available",
"hookSpecificOutput": {
"hookEventName": "UserPromptExpansion",
"additionalContext": "Additional context for this expansion"
}
}
MessageDisplay
在助理訊息串流到螢幕上時執行。Claude Code 會分段顯示訊息:每當一批新完成的行準備好呈現時,hook 就會以這些行執行一次,而 Claude Code 會在其位置呈現 hook 的替換文字。長訊息會產生多次呼叫;短訊息可能只產生一次。
使用 MessageDisplay 來:
- 移除 markdown 以精簡顯示
- 轉換 Agent SDK 應用程式向其使用者顯示的文字
- 從 Claude 的回應中遮蔽 API 金鑰或內部主機名稱
Claude Code 會保留每一批內容直到您的 hook 回傳,因此請讓 hook 保持快速。如果 hook 失敗或逾時,Claude Code 會顯示原始文字。此事件的預設逾時為 10 秒;如果您的 hook 需要更多時間,請在 hook 項目中設定 timeout 欄位。
MessageDisplay 僅影響顯示:替換文字只會改變螢幕上呈現的內容。逐字稿與 Claude 看到的內容會保留原始文字,因此 Claude 永遠不會看到替換內容,而詳細模式會顯示原始內容。hook 只接收助理訊息文字,因此工具結果與您輸入的文字會原樣呈現。
MessageDisplay 不支援 matcher,會對每一則串流文字的助理訊息觸發;沒有文字的訊息,例如只有工具呼叫的回應,不會觸發它。
在非互動式執行中,包括 Agent SDK 查詢與 claude -p,MessageDisplay 會針對每則助理訊息執行一次,而非每批行執行一次。這次單一呼叫會在訊息完成後送達,並攜帶完整的訊息文字:index 為 0、final 為 true,而 delta 包含整則訊息。為每則訊息收集 delta 文字的 hook,在兩種模式下都會接收到相同的完整文字。
MessageDisplay 輸入
除了通用輸入欄位之外,MessageDisplay hook 還會接收回合與訊息的識別碼、此次呼叫在訊息中的位置,以及 delta 中的新文字。批次邊界取決於文字的串流方式,因此請使用 index 與 final 追蹤訊息的進度,而不要預期行會以特定方式分組。
| 欄位 | 說明 |
|---|---|
turn_id |
目前回合的 UUID |
message_id |
正在顯示的助理訊息的 UUID。在同一則訊息的每一批中保持不變。這不是 API 的 msg_… id,因此無法與逐字稿的訊息 id 對應 |
index |
此批次在訊息中從零起算的索引 |
final |
在訊息的最後一批時為 true。每則訊息恰好有一個最後批次 |
delta |
自前一批以來新完成的行,包含結尾的換行字元。一律為完整的行,但最後一批可能在行中間結束。在互動式執行中,當訊息以換行字元結束時,最後一批的 delta 為空,因此請將 final(而非非空的 delta)視為訊息結束的訊號。在 Agent SDK 與 claude -p 執行中,單一呼叫會攜帶整則訊息 |
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "MessageDisplay",
"turn_id": "0c9e6a2f-7d41-4f4e-9a15-3f4f7c2b8d10",
"message_id": "5b2a9c8e-1f63-4d8a-b7c4-9e0d2a6f1c3b",
"index": 0,
"final": false,
"delta": "Here is the plan:\n"
}
MessageDisplay 輸出
除了所有 hook 都可使用的 JSON 輸出欄位之外,MessageDisplay hook 還可以回傳 displayContent,以在螢幕上替換 delta:
| 欄位 | 說明 |
|---|---|
displayContent |
取代 delta 顯示的文字。省略則顯示原始內容 |
MessageDisplay hook 沒有決策控制。它們無法阻擋訊息,也無法變更儲存在逐字稿中或傳送給 Claude 的內容。Claude Code 會依據其 JSON 輸出中的 displayContent 採取動作,並捨棄 systemMessage 與 continue。
此範例會從 Claude 的回應中移除 markdown 格式,以純文字顯示。指令碼從 stdin 讀取每一批內容,從 delta 中移除粗體標記與行內程式碼反引號,並將結果以 displayContent 回傳。
在您的設定檔中為此事件註冊 command hook:
{
"hooks": {
"MessageDisplay": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.sh",
"args": []
}
]
}
]
}
}
將此指令碼儲存至專案中的 .claude/hooks/plain-display.sh,並以 chmod +x 使其可執行:
#!/bin/bash
jq '{hookSpecificOutput: {hookEventName: "MessageDisplay", displayContent: (.delta | gsub("\\*\\*"; "") | gsub("`"; ""))}}'
註冊透過 PowerShell 執行指令碼的 command hook:
{
"hooks": {
"MessageDisplay": [
{
"hooks": [
{
"type": "command",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.ps1"
]
}
]
}
]
}
}
-NoProfile 旗標會略過載入您的 PowerShell 設定檔,讓 hook 快速啟動,而 -ExecutionPolicy Bypass 讓 PowerShell 能執行本機指令碼檔案。
將此指令碼儲存至專案中的 .claude/hooks/plain-display.ps1:
$batch = [Console]::In.ReadToEnd() | ConvertFrom-Json
$text = $batch.delta -replace '\*\*', '' -replace '`', ''
@{
hookSpecificOutput = @{
hookEventName = "MessageDisplay"
displayContent = $text
}
} | ConvertTo-Json
不含 markdown 的批次會原樣通過。如果指令碼失敗,例如因為缺少 jq,Claude Code 會顯示原始文字,且只在偵錯輸出中記錄失敗,而不會在工作階段中顯示。
PreToolUse
在 Claude 建立工具參數之後、處理工具呼叫之前執行。比對 EndConversation 以外的任何工具名稱:內建工具,例如 Bash、PowerShell、Edit、Write、Read、Glob、Grep、Agent、Workflow、WebFetch、WebSearch、AskUserQuestion 與 ExitPlanMode,以及任何 MCP 工具名稱。
若要在特定檔案於磁碟上變更時執行 hook(無論由誰寫入),請使用 FileChanged,而不要依名稱比對編輯檔案的工具。與 PreToolUse 不同,Claude Code 會在變更之後執行 FileChanged hook,且它們沒有決策控制,因此無法阻擋寫入。
PreToolUse 只會在 Claude 呼叫工具時執行。您在提示詞中以 @ 參照的檔案會在沒有任何工具呼叫的情況下加入:Claude Code 在建構提示詞時插入其內容,因此不會為它們觸發任何 PreToolUse hook,包括比對 Read 的 hook。若要阻擋特定路徑被 @ 參照,請改用 Read 拒絕規則。
PreToolUse 也不會為 EndConversation 觸發。
使用 PreToolUse 決策控制來允許、拒絕、詢問或延後工具呼叫。
PreToolUse 上超過逾時的 Agent SDK 回呼 hook 會阻擋工具呼叫,而 Claude 會收到指出該逾時的錯誤結果。其他 hook 回傳的明確拒絕仍優先於此。
PreToolUse 輸入
除了通用輸入欄位之外,PreToolUse hook 還會接收 tool_name、tool_input 與 tool_use_id。
對於 MCP 工具,輸入還會攜帶 mcp_server,這是一個包含伺服器 name 以及 source 的物件,source 說明伺服器的定義來自何處。source 值包括 plugin、sdk,以及 user 與 project 等設定範圍。Agent SDK 參考文件中的 McpServerProvenance 列出了所有值,並說明如何處理無法辨識的值。請依據 source 而非 name 或 mcp__<server>__ 工具名稱前綴做出信任決策。mcp_server 欄位需要 Claude Code v2.1.274 或更新版本。
對於檔案工具 Write、Edit 與 Read,tool_input.file_path 一律為絕對路徑:
- Claude Code 會在 hook 執行之前展開
~與相對路徑,因此比對路徑的 hook 無法透過~或同一路徑的相對寫法被繞過 - 在 Windows 上,路徑會以反斜線分隔符號傳入,即使您的 hook 在 Git Bash 下執行、其中
$PWD看起來像/c/project也是如此 - 以正斜線撰寫的比較,例如
/src/檢查,永遠不會與反斜線路徑相符,工具呼叫會如同 hook 沒有可阻擋的內容般繼續 - 請在比較前將分隔符號正規化:在 Bash 中使用
FILE_PATH="${FILE_PATH//\\//}",在 Python 中使用file_path.replace("\\", "/"),然後比對路徑片段,例如/src/,而不要以^錨定,因為路徑是絕對路徑
Windows 上的 Write 呼叫會傳遞:
{
"hook_event_name": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "C:\\project\\src\\index.ts",
"content": "..."
},
...
}
tool_input 欄位取決於工具:
Bash
執行 shell 命令。
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
command |
string | "npm test" |
要執行的 shell 命令 |
description |
string | "Run test suite" |
選擇性的命令用途說明 |
timeout |
number | 120000 |
選擇性的逾時(毫秒)。超過上限的值會被降為上限,而不會被拒絕 |
run_in_background |
boolean | false |
是否在背景執行命令 |
當 Bash 命令變更 Git 儲存庫中的檔案時,Claude Code 可以記錄變更內容。當 bashEditDiffEnabled 設定開啟記錄時,它會在每種權限模式下記錄變更;該設定的項目說明了哪些檔案可以設定它。否則,它只會在自動模式與 bypassPermissions 模式下記錄,且僅在 Claude Code 指示 Claude 透過 Bash 編輯檔案時記錄。將 bashEditDiffEnabled 設為 false 即可關閉記錄。背景命令與唯讀命令不會攜帶差異。
接著,您的 PostToolUse hook 會在 tool_response.bashEditDiff 中接收已變更的檔案。清單涵蓋命令執行期間儲存庫下的變更。Git 忽略的檔案與子模組中的檔案不會列出。需要 Claude Code v2.1.269 或更新版本。
此清單為盡力而為,且處於公開測試版。Claude Code 可能遺漏變更、包含同時被其他程序變更的檔案,或在達到大小限制時停止。欄位結構可能會變更。請使用此清單找出需要審查的內容,而不要用來強制執行政策。
changedFiles 與 files 列出命令變更的內容;其餘欄位說明該清單的完整程度與可靠程度。
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
changedFiles |
array | ["/path/to/src/app.ts"] |
命令變更的檔案絕對路徑,最多 200 個。只要 files 包含差異或 moreFiles 大於零就會出現 |
files |
array | [{"filePath": "/path/to/src/app.ts", "hunks": [...]}] |
最多 5 個已變更檔案的差異,供顯示用。對於命令新增或移除的檔案,created 或 deleted 為 true |
moreFiles |
number | 2 |
在 files 中沒有差異的已變更檔案數 |
unavailable |
boolean | true |
當差異不完整或無法取得時設定 |
skipped |
boolean | true |
針對會移動工作樹的 Git 命令設定,例如 git checkout 或 git stash,因此 Claude Code 不會取得差異 |
shared |
boolean | true |
當另一個 Bash 工具呼叫(例如 subagent 的呼叫)同時在同一個儲存庫中執行時設定,因此部分列出的變更可能來自該命令 |
PowerShell
執行 PowerShell 命令。關於各平台的可用性,請參閱 PowerShell 工具。
欄位與 Bash 工具相同,命令字串位於 command:
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
command |
string | "Get-ChildItem -Recurse" |
要執行的 PowerShell 命令 |
description |
string | "List files recursively" |
選擇性的命令用途說明 |
timeout |
number | 120000 |
選擇性的逾時(毫秒) |
run_in_background |
boolean | false |
是否在背景執行命令 |
在檢查 shell 命令的 hook 中請比對 Bash|PowerShell,以涵蓋兩種工具:
- 在 Windows 上,只要啟用了 PowerShell 工具,Claude 就會將 PowerShell 視為主要 shell,並透過它執行 shell 命令。
- 在沒有 Git Bash 的 Windows 上,該工具會自動啟用,而 Claude Code 完全不會註冊 Bash 工具。
- 只比對
Bash的 hook 在那裡永遠不會觸發。
Write
建立或覆寫檔案。
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
file_path |
string | "/path/to/file.txt" |
要寫入的檔案絕對路徑 |
content |
string | "file content" |
要寫入檔案的內容 |
Edit
取代現有檔案中的字串。
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
file_path |
string | "/path/to/file.txt" |
要編輯的檔案絕對路徑 |
old_string |
string | "original text" |
要尋找並取代的文字 |
new_string |
string | "replacement text" |
取代文字 |
replace_all |
boolean | false |
是否取代所有出現處 |
Read
讀取檔案內容。
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
file_path |
string | "/path/to/file.txt" |
要讀取的檔案絕對路徑 |
offset |
number | 10 |
選擇性的起始讀取行號 |
limit |
number | 50 |
選擇性的讀取行數 |
Glob
尋找符合 glob 模式的檔案。
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
pattern |
string | "**/*.ts" |
用來比對檔案的 glob 模式 |
path |
string | "/path/to/dir" |
選擇性的搜尋目錄。預設為目前工作目錄 |
Grep
以規則運算式搜尋檔案內容。
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
pattern |
string | "TODO.*fix" |
要搜尋的規則運算式模式 |
path |
string | "/path/to/dir" |
選擇性的搜尋檔案或目錄 |
glob |
string | "*.ts" |
選擇性的檔案篩選 glob 模式 |
output_mode |
string | "content" |
"content"、"files_with_matches" 或 "count"。預設為 "files_with_matches" |
-i |
boolean | true |
不區分大小寫搜尋 |
multiline |
boolean | false |
啟用多行比對 |
WebFetch
擷取並處理網頁內容。
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
url |
string | "https://example.com/api" |
要擷取內容的 URL |
prompt |
string | "Extract the API endpoints" |
要對擷取內容執行的提示詞 |
WebSearch
搜尋網路。
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
query |
string | "react hooks best practices" |
搜尋查詢 |
allowed_domains |
array | ["docs.example.com"] |
選擇性:只包含來自這些網域的結果 |
blocked_domains |
array | ["spam.example.com"] |
選擇性:排除來自這些網域的結果 |
Agent
產生一個 subagent。
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
prompt |
string | "Find all API endpoints" |
agent 要執行的任務 |
description |
string | "Find API endpoints" |
任務的簡短說明 |
subagent_type |
string | "Explore" |
要使用的專門 agent 類型 |
model |
string | "sonnet" |
選擇性的模型別名,用以覆寫預設值 |
當前景 Agent 呼叫完成時,您的 PostToolUse hook 會在 tool_response 中接收 subagent 的結果與執行遙測。請讀取這些欄位來檢視執行情況;若要彙總各 subagent 的 token 與成本,請使用以 query_source "subagent" 篩選的 token 與成本計數器,因為 totalTokens 與 usage 只涵蓋最後一個請求:
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
status |
string | "completed" |
前景 subagent 為 "completed",背景 subagent 為 "async_launched"。subagent 預設在背景執行,因此省略 run_in_background 的 Agent 呼叫也會產生 "async_launched" |
agentId |
string | "a4d2c8f1e0b3a297" |
subagent 執行的識別碼 |
content |
array | [{"type": "text", "text": "Found 12 endpoints..."}] |
subagent 最終的文字區塊;若 subagent 的報告是透過 SubagentHandback 傳遞,則改為關於該交回的簡短說明 |
resolvedModel |
string | "claude-sonnet-4-5" |
subagent 開始時使用的模型,可能與所請求的模型不同 |
modelsUsed |
array | ["claude-sonnet-4-5", "claude-haiku-4-5"] |
依序使用的模型,連續重複的項目會合併;僅在執行途中切換模型時設定。需要 Claude Code v2.1.212 或更新版本 |
totalTokens |
number | 12450 |
subagent 最後一個 API 請求的 token 數:輸入、輸出與快取 token 的總和。這並非整個執行的總計 |
totalDurationMs |
number | 48211 |
subagent 執行的實際時間長度 |
totalToolUseCount |
number | 7 |
subagent 進行的工具呼叫次數 |
usage |
object | {"input_tokens": 8320, ...} |
最後一個 API 請求依類型的 token 明細:input_tokens、output_tokens、cache_creation_input_tokens、cache_read_input_tokens |
在 Claude Code v2.1.271 或更新版本中,使用 SubagentHandback 工具(Claude Code 在自動模式中提供)執行的 subagent,會透過該工具傳遞其報告,而非以文字回傳。此時其 completed 結果的 content 欄位攜帶的是關於該交回的簡短說明,而非報告本身。若要讀取報告,請讓 PreToolUse 或 PostToolUse hook 比對 SubagentHandback,並讀取 tool_input.message。
對於背景 subagent,工具會在任務移至背景時回傳,因此 tool_response 不攜帶使用量欄位:背景啟動會立即回傳,而 Claude Code 在執行途中移至背景的前景任務則會在該轉換時回傳。它包含 status: "async_launched"、agentId、description、prompt、outputFile 與 resolvedModel。
在 completed 回應中,resolvedModel 指出 subagent 開始時使用的模型,可能與 tool_input 中的 model 值不同,例如套用了 availableModels 或其他覆寫時。在 async_launched 回應中,resolvedModel 指出 agent 移至背景時使用中的模型,因此在移至背景之前發生的切換會反映在此。modelsUsed 以及移至背景時的 resolvedModel 行為需要 Claude Code v2.1.212 或更新版本。
AskUserQuestion
向使用者詢問一到四個選擇題。
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
questions |
array | [{"question": "Which framework?", "header": "Framework", "options": [{"label": "React"}], "multiSelect": false}] |
要呈現的問題,每個問題包含 question 字串、簡短的 header、options 陣列,以及選擇性的 multiSelect 旗標 |
answers |
object | {"Which framework?": "React"} |
選擇性。將問題文字對應到所選選項的標籤。多選答案會以逗號連接標籤。Claude 不會設定此欄位;請透過 updatedInput 提供,以程式化方式作答 |
ExitPlanMode
在 Claude 離開 plan mode 之前呈現計畫並請使用者核准。Claude 在呼叫工具之前會將計畫寫入磁碟上的檔案,因此來自模型的原始 tool_input 通常為空。Claude Code 會在將輸入傳遞給 hook 之前注入計畫內容與檔案路徑。
| 欄位 | 類型 | 範例 | 說明 |
|---|---|---|---|
plan |
string | "## Refactor auth\n1. Extract..." |
Markdown 格式的計畫內容。從磁碟上的計畫檔案注入 |
planFilePath |
string | "/Users/.../plans/refactor-auth.md" |
計畫檔案的路徑。為注入值 |
allowedPrompts |
array | [{"tool": "Bash", "prompt": "run tests"}] |
已棄用。Claude Code 接受此欄位但會忽略它。在 v2.1.205 之前,它攜帶 Claude 為實作計畫而請求的提示詞式權限 |
在 PostToolUse 中,tool_response 是一個物件,包含存放已核准計畫的 plan 與 filePath 欄位,以及內部狀態旗標。請讀取 tool_response.plan 取得計畫內容,而不要從磁碟重新讀取檔案。
PreToolUse 決策控制
PreToolUse hook 可以控制工具呼叫是否繼續。與其他使用頂層 decision 欄位的 hook 不同,PreToolUse 會在 hookSpecificOutput 物件內回傳其決策。這賦予它更豐富的控制:四種結果(允許、拒絕、詢問或延後),以及在執行前修改工具輸入的能力。
| 欄位 | 說明 |
|---|---|
permissionDecision |
"allow" 會略過權限提示,但任何模式都不會自動核准的動作,以及需要搭配 updatedInput 的 AskUserQuestion 與 ExitPlanMode 除外。"deny" 會阻止工具呼叫。"ask" 會提示使用者確認。"defer" 會正常結束,以便稍後繼續執行該工具。無論 hook 回傳什麼,拒絕與詢問規則仍會被評估 |
permissionDecisionReason |
對於 "ask",會在權限提示中向使用者顯示。當 Claude Code 在無人能回應該提示的 -p 執行中拒絕呼叫時,Claude 會改在工具結果中讀取該原因。對於 "deny",會向 Claude 顯示。對於 "allow" 與 "defer",只會寫入偵錯日誌 |
updatedInput |
在執行前修改工具的輸入參數。會取代整個輸入物件,因此請將未變更的欄位與修改後的欄位一併包含。Claude Code 會針對您的 hook 回傳的輸入(而非 Claude 傳送的輸入)評估權限規則以及 Bash 命令的自動移至背景資格。與 "allow" 搭配可自動核准,或與 "ask" 搭配以向使用者顯示修改後的輸入。對於 "defer" 則會被忽略 |
additionalContext |
與工具結果一同加入 Claude 上下文的字串。當 permissionDecision 為 "defer" 時會被忽略。請參閱為 Claude 加入上下文 |
當多個 PreToolUse hook 回傳不同決策時,優先順序為 deny > defer > ask > allow。
以退出碼 2 阻擋的 hook 與 "deny" 的處理方式相同:Claude 會將 stderr 訊息視為拒絕原因。
當 hook 回傳 "ask" 時,向使用者顯示的權限提示會包含一個標籤,標示該 hook 的來源:來自任何設定檔或 agent frontmatter 的 hook 為 [settings],外掛的 hook 為 [plugin:<name>],來自 skill frontmatter 的 hook 則為 [skill]。這有助於使用者了解是哪個設定來源在請求確認。
hook 的 "ask" 也會在自動模式中強制顯示權限提示:分類器仍可拒絕工具呼叫,但無法在無提示的情況下核准該呼叫。在 v2.1.211 之前,分類器可以在不顯示 hook 所請求之提示的情況下,核准在沙箱外執行的 Bash 命令;分類器仍會對該命令套用其自身的安全規則,而 hook 的 "deny" 一律會被遵守。
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "My reason here",
"updatedInput": {
"field_to_modify": "new value"
},
"additionalContext": "Current environment: production. Proceed with caution."
}
}
在使用 -p 旗標的非互動模式中,只有當執行具有接收提示的權限主機(例如 Agent SDK 的 canUseTool 回呼)時,Claude Code 才會提供 AskUserQuestion 與 ExitPlanMode。這些工具需要使用者互動。回傳 permissionDecision: "allow" 並搭配 updatedInput 即可滿足此需求:hook 從 stdin 讀取工具的輸入,透過您自己的 UI 收集答案,並在 updatedInput 中回傳,讓工具在不提示的情況下執行。對於這些工具,只回傳 "allow" 是不夠的。對於 AskUserQuestion,請回傳原始的 questions 陣列,並加入一個 answers 物件,將每個問題的文字對應到所選的答案。
伺服器以 _meta["anthropic/requiresUserInteraction"] 標記的 MCP 工具則更為嚴格:hook 無法以 "allow" 略過其核准提示,無論是否搭配 updatedInput,因為 Claude Code 無法確認 hook 已收集該工具所需的互動。
PreToolUse 先前使用頂層的 decision 與 reason 欄位,但這些欄位在此事件中已棄用。請改用 hookSpecificOutput.permissionDecision 與 hookSpecificOutput.permissionDecisionReason。已棄用的值 "approve" 與 "block" 分別對應到 "allow" 與 "deny"。PostToolUse 與 Stop 等其他事件則繼續以頂層 decision 與 reason 作為其目前的格式。
延後工具呼叫
"defer" 適用於以子程序執行 claude -p 並讀取其 JSON 輸出的整合,例如 Agent SDK 應用程式或建立在 Claude Code 之上的自訂 UI。它讓呼叫端程序可以在工具呼叫處暫停 Claude、透過自己的介面收集輸入,並從中斷處繼續。Claude Code 只在使用 -p 旗標的非互動模式中遵守此值。在互動式工作階段中,它會記錄警告並忽略 hook 結果。
AskUserQuestion 工具是典型的情況:Claude 想向使用者詢問某件事,但沒有可供回答的終端機。-p 執行只有在具有權限主機(例如您以 --permission-prompt-tool 傳入的 MCP 工具)時才會提供 AskUserQuestion,因此請以權限主機啟動執行。往返流程如下:
- Claude 呼叫
AskUserQuestion。PreToolUsehook 觸發。 - hook 回傳
permissionDecision: "defer"。工具不會執行。程序以stop_reason: "tool_deferred"結束,待處理的工具呼叫會保留在逐字稿中。 - 呼叫端程序從 SDK 結果讀取
deferred_tool_use,在自己的 UI 中呈現問題,並等待答案。 - 呼叫端程序以相同的權限主機執行
claude -p --resume <session-id>。同一個工具呼叫會再次觸發PreToolUse。 - hook 回傳
permissionDecision: "allow",並在updatedInput中附上答案。工具執行,Claude 繼續。
deferred_tool_use 欄位攜帶工具的 id、name 與 input。input 是 Claude 為該工具呼叫產生的參數,在執行前擷取:
{
"type": "result",
"subtype": "success",
"stop_reason": "tool_deferred",
"session_id": "abc123",
"deferred_tool_use": {
"id": "toolu_01abc",
"name": "AskUserQuestion",
"input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React"}, {"label": "Vue"}], "multiSelect": false }] }
}
}
沒有逾時或重試次數限制。工作階段會保留在磁碟上直到您繼續它,但受 cleanupPeriodDays 保留清理的限制,該清理預設會在 30 天後刪除工作階段檔案,並遵循保留清理規則。如果繼續時答案尚未準備好,hook 可以再次回傳 "defer",程序會以相同方式結束。呼叫端程序藉由最終從 hook 回傳 "allow" 或 "deny" 來控制何時跳出迴圈。
"defer" 只在 Claude 於該回合中只進行一次工具呼叫時有效。如果 Claude 同時進行多個工具呼叫,"defer" 會被忽略並發出警告,工具會透過一般的權限流程繼續。此限制的原因在於繼續時只能重新執行一個工具:無法在不讓其他呼叫懸而未決的情況下,延後批次中的其中一個呼叫。
如果繼續時延後的工具已不可用,程序會在 hook 觸發之前以 stop_reason: "tool_deferred_unavailable" 與 is_error: true 結束。當提供該工具的 MCP 伺服器在繼續的工作階段中未連線時,就會發生這種情況。deferred_tool_use payload 仍會包含在內,讓您能識別遺失的是哪個工具。
若要在 plan mode 中繼續已延後的工作階段,請將 --permission-prompt-tool 與 --resume 一併傳入,讓 Claude Code 能呈現計畫以供核准。如果您傳入某些其他啟動旗標,繼續的執行不會回到 plan mode;請參閱使用 -p 在 plan mode 中繼續。需要 Claude Code v2.1.246 或更新版本。
當您以 -p 繼續時,Claude Code 不會還原任何其他已儲存的權限模式。它會以新的 claude -p 執行所使用的權限模式啟動執行,因此如果延後的工作階段使用了 --permission-mode 或 --dangerously-skip-permissions,請再次傳入。當您不使用 -p 而以 claude --resume <session-id> 繼續時,Claude Code 會還原已儲存的權限模式,例外情況列於繼續時的權限模式。
PermissionRequest
在 Claude Code 即將向您請求使用工具的權限時執行。在無法顯示提示的工作階段中,例如非互動模式中的背景 subagent,Claude Code 仍會執行這些 hook,而如果沒有任何 hook 回傳決策,它就會拒絕該工具呼叫。對於到達 --permission-prompt-tool 或 Agent SDK 的 canUseTool 回呼的呼叫,hook 會與您的主機並行執行,以先做出決定者為準。
使用 PermissionRequest 決策控制代表使用者允許或拒絕。
當您需要在 Claude 請求使用工具權限的當下取得訊號時,請使用此事件。Claude Code 只有在提示已等待約六秒後,才會執行類型為 permission_prompt 的 Notification hook。
Claude Code 不會為沙箱化命令的網路請求執行 PermissionRequest hook。若要取得該提示的訊號,請使用 permission_prompt 通知類型。
比對工具名稱,值與 PreToolUse 相同。
PermissionRequest 輸入
PermissionRequest hook 會像 PreToolUse hook 一樣接收 tool_name 與 tool_input 欄位,但不含 tool_use_id。對於 MCP 工具,它們也會接收 mcp_server 物件。選擇性的 permission_suggestions 陣列包含 Claude Code 為此請求建議的權限更新,例如新增允許規則或變更權限模式。
permission_suggestions 陣列並非您所看到選項的精確清單,因為每個權限對話框會建構自己的選項。有些對話框(例如檔案編輯的對話框)完全不讀取此陣列,而是從請求本身衍生選項。會讀取此陣列的對話框,仍可能隱藏其建議保留在陣列中的選項,例如當 allowManagedPermissionRulesOnly 隱藏儲存規則的選項時。它也可能提供沒有對應建議項目的選項,例如 Yes, and switch to auto mode,它會直接變更權限模式,而非透過權限更新。
PreToolUse hook 會在每次工具呼叫之前執行,無論是否需要權限。PermissionRequest hook 只在 Claude Code 即將向您請求權限時執行,或在它原本會自動拒絕一個無法提示的呼叫時執行。兩個事件都不會為 EndConversation 觸發。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PermissionRequest",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf node_modules",
"description": "Remove node_modules directory"
},
"permission_suggestions": [
{
"type": "addRules",
"rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
"behavior": "allow",
"destination": "localSettings"
}
]
}
PermissionRequest 決策控制
PermissionRequest hook 可以允許或拒絕權限請求。除了所有 hook 都可使用的 JSON 輸出欄位之外,您的 hook 指令碼還可以回傳包含下列事件專屬欄位的 decision 物件:
| 欄位 | 說明 |
|---|---|
behavior |
"allow" 授予權限,"deny" 拒絕權限。拒絕與詢問規則仍會被評估,因此回傳 "allow" 的 hook 不會覆寫相符的拒絕規則 |
updatedInput |
僅適用於 "allow":在執行前修改工具的輸入參數。會取代整個輸入物件,因此請將未變更的欄位與修改後的欄位一併包含。修改後的輸入會重新針對拒絕與詢問規則評估 |
updatedPermissions |
僅適用於 "allow":要套用的權限更新項目陣列,例如新增允許規則或變更工作階段權限模式 |
message |
僅適用於 "deny":告訴 Claude 權限被拒絕的原因 |
interrupt |
僅適用於 "deny":若為 true,則停止 Claude |
以退出碼 2 結束但沒有 decision 物件的 hook 不會改變權限流程,其 stderr 會被捨棄。只有 decision 物件能授予或拒絕請求。
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": {
"command": "npm run lint"
}
}
}
}
權限更新項目
updatedPermissions 輸出欄位與 permission_suggestions 輸入欄位都使用相同的項目物件陣列。每個項目都有一個決定其他欄位的 type,以及一個控制變更寫入位置的 destination。
type |
欄位 | 效果 |
|---|---|---|
addRules |
rules、behavior、destination |
新增權限規則。rules 是 {toolName, ruleContent?} 物件的陣列。省略 ruleContent 即可比對整個工具。behavior 為 "allow"、"deny" 或 "ask" |
replaceRules |
rules、behavior、destination |
以提供的 rules 取代 destination 中所有指定 behavior 的規則 |
removeRules |
rules、behavior、destination |
移除指定 behavior 的相符規則 |
setMode |
mode、destination |
變更權限模式。有效的模式為 default、auto、acceptEdits、dontAsk、bypassPermissions、plan,以及作為 default 別名的 manual。manual 別名需要 Claude Code v2.1.200 或更新版本 |
addDirectories |
directories、destination |
新增工作目錄。directories 是路徑字串的陣列 |
removeDirectories |
directories、destination |
移除工作目錄 |
只有在啟動工作階段時已可使用略過模式的情況下,搭配 bypassPermissions 的 setMode 才會生效:--dangerously-skip-permissions、--permission-mode bypassPermissions、--allow-dangerously-skip-permissions,或在使用者設定、--settings 或受管設定中設定 permissions.defaultMode: "bypassPermissions"。否則此更新不會產生任何作用。當 permissions.disableBypassPermissionsMode 停用此模式,或工作階段以受限模式啟動時,此更新同樣不會產生任何作用。
無論 destination 為何,bypassPermissions 都不會被保存為 defaultMode。
每個項目上的 destination 欄位決定變更是保留在記憶體中,還是保存到設定檔。
destination |
寫入位置 |
|---|---|
session |
僅存在記憶體中,工作階段結束時捨棄 |
localSettings |
.claude/settings.local.json |
projectSettings |
.claude/settings.json |
userSettings |
~/.claude/settings.json |
hook 可以將其收到的某個 permission_suggestions 原樣作為自己的 updatedPermissions 輸出傳回。
PostToolUse
在工具成功完成後立即執行。
依工具名稱比對,可用值與 PreToolUse 相同。
當工具名稱不是合適的篩選條件時,可以更廣泛地比對:
- 若要在任何工具成功完成後執行 hook,請省略
matcher或將其設為"*"。您的 hook 接著可以自行找出變更內容,例如執行git status --porcelain,它也會列出git diff遺漏的未追蹤檔案。對於失敗的工具呼叫,請在 PostToolUseFailure 下新增相同的 hook。 - 若要在特定檔案於磁碟上變更時執行 hook(無論由誰寫入),請使用 FileChanged。當
Bash命令或 Claude Code 以外的程序改寫同一個檔案時,Claude Code 不會執行比對Edit|Write的PostToolUsehook。
PostToolUse 輸入
PostToolUse hook 會在工具已成功執行後觸發。輸入同時包含 tool_input(傳送給工具的引數)與 tool_response(工具傳回的結果)。兩者的確切 schema 取決於工具。檔案工具的 tool_input 路徑格式與 PreToolUse 相同:一律為絕對路徑,並使用平台原生的分隔符號,因此在 Windows 上為反斜線。對於 MCP 工具,輸入也會帶有 mcp_server 物件。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.txt",
"content": "file content"
},
"tool_response": {
"filePath": "/path/to/file.txt",
"type": "create"
},
"tool_use_id": "toolu_01ABC123...",
"duration_ms": 12
}
| 欄位 | 說明 |
|---|---|
duration_ms |
選用。工具執行時間(毫秒)。不包含花在權限提示與 PreToolUse hook 上的時間 |
PostToolUse 決策控制
PostToolUse hook 可以在工具執行後向 Claude 提供回饋。除了所有 hook 皆可使用的 JSON 輸出欄位之外,您的 hook 指令碼還可以傳回以下事件專屬欄位:
| 欄位 | 說明 |
|---|---|
decision |
"block" 會將 reason 附加在工具結果旁。Claude 仍會看到原始輸出;若要取代輸出,請使用 updatedToolOutput |
reason |
當 decision 為 "block" 時向 Claude 顯示的說明 |
additionalContext |
與工具結果一併加入 Claude 上下文的字串。請參閱為 Claude 新增上下文 |
classifierContext |
關於此次呼叫結果的簡短註記,提供給自動模式分類器而非 Claude。請參閱為自動模式分類器註記結果。需要 Claude Code v2.1.236 或更新版本 |
updatedToolOutput |
在傳送給 Claude 之前,以提供的值取代工具的輸出。該值必須符合工具的輸出結構 |
updatedMCPToolOutput |
僅取代 MCP 工具的輸出。建議改用適用於所有工具的 updatedToolOutput |
以下範例取代 Bash 呼叫的輸出。取代值符合 Bash 工具的輸出結構:
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Additional information for Claude",
"updatedToolOutput": {
"stdout": "[redacted]",
"stderr": "",
"interrupted": false,
"isImage": false
}
}
}
updatedToolOutput 只會改變 Claude 看到的內容。hook 觸發時工具已經執行完畢,因此任何已寫入的檔案、已執行的命令或已送出的網路請求都已生效。OpenTelemetry 工具 span 與分析事件等遙測資料,也會在 hook 執行前擷取原始輸出。若要在工具呼叫執行前阻止或修改它,請改用 PreToolUse hook。
取代值必須符合工具的輸出結構。內建工具傳回的是結構化物件,而非純字串。例如,Bash 會傳回包含 stdout、stderr、interrupted 與 isImage 欄位的物件。對於內建工具,不符合工具輸出 schema 的值會被忽略,並改用原始輸出。MCP 工具的輸出則會直接傳遞,不進行 schema 驗證。移除 Claude 所需的錯誤細節,可能導致它依據錯誤的假設繼續進行。
為自動模式分類器註記結果
傳回 classifierContext,即可將關於工具呼叫結果的簡短註記傳送給自動模式分類器,而非傳送給 Claude。分類器永遠不會收到工具結果本身,因此在它審查後續動作之前,此欄位是告知它某次呼叫傳回內容的官方支援方式。此欄位需要 Claude Code v2.1.236 或更新版本。
以下範例告訴分類器某個查詢的輸出來源:
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"classifierContext": "This query ran against the staging database, not production."
}
}
分類器對註記的重視程度,取決於您在何處設定該 hook:
- 在 Claude Code 中設定的 hook:對於來自設定檔、外掛、skill 與 agent frontmatter 的 hook,分類器會將註記視為未經驗證、由應用程式提供的上下文。註記永遠無法確立使用者意圖;若註記聲稱您核准或要求了某件事,分類器會以您在對話中的訊息查核該聲明
- 行程內 Agent SDK 回呼:當嵌入 Claude Code 的應用程式將 hook 註冊為 TypeScript SDK 回呼,並在即時工作階段中傳回註記時,分類器可能會將註記中轉述的使用者陳述視為使用者意圖。此類陳述可以滿足分類器原本會接受您傳送訊息來滿足的同意要求,但永遠無法解除您自己的訊息也無法解除的封鎖。工作階段恢復後,Claude Code 會將還原的註記視為未經驗證的上下文。當兩組來源的 hook 都為同一次呼叫加上註記時,分類器會將合併後的註記視為未經驗證
Claude Code 在傳遞註記時會套用以下限制:
- 長度:Claude Code 將單次工具呼叫的註記上限設為 2,000 個字元,超出部分會被截斷。此上限由回應該次呼叫的所有 hook 共用
- 僅限同步回應:對於在背景執行的 hook,Claude Code 會忽略其回應中的此欄位,因為該回應會在 Claude Code 記錄工具結果之後才抵達
- 分類器未記錄的呼叫:分類器的逐字稿會省略唯讀查詢,例如檔案讀取與搜尋。附加在這類呼叫上的註記會被 Claude Code 捨棄
- 與改寫的互動:當註記描述的是您正以
updatedToolOutput取代的輸出時,請在同一個 hook 回應中同時傳回這兩個欄位。若該改寫遭到拒絕,或被另一個 hook 的改寫取代,Claude Code 會捨棄該註記。若您傳回註記但未改寫,即使另一個 hook 改寫了輸出,Claude Code 仍會傳遞您的註記
分類器會將您放入 classifierContext 的內容視為來自託管工作階段之應用程式的資訊,因此請勿將不受信任的工具輸出或第三方文字複製到其中。請將註記限制為關於這一次呼叫的簡短陳述,例如其來源的相關事實,或使用者對其的陳述;請勿使用此欄位傳遞無關的訊息或一連串事件。
PostToolUseFailure
當已開始執行的工具失敗時執行:工具擲出錯誤,或 MCP 工具傳回錯誤結果。可用來記錄失敗、傳送警示,或向 Claude 提供修正回饋。
依工具名稱比對,可用值與 PreToolUse 相同。
對於在執行前即遭拒絕的工具呼叫,此事件不會觸發:未知的工具名稱、未通過 schema 或工具專屬驗證的輸入,或權限遭拒。驗證拒絕會以 tool_use_error 結果傳回,且發生在 hook 執行之前,因此既不會觸發 PreToolUse,也不會觸發 PostToolUseFailure。權限遭拒會觸發 PreToolUse,但不會觸發此事件;請參閱 PermissionDenied。
PostToolUseFailure 輸入
PostToolUseFailure hook 會收到與 PostToolUse 相同的 tool_name 與 tool_input 欄位,以及作為頂層欄位的錯誤資訊。對於 MCP 工具,它們也會收到 mcp_server 物件。例如,失敗的 npm test 命令可能會傳遞:
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolUseFailure",
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "Run test suite"
},
"tool_use_id": "toolu_01ABC123...",
"error": "Exit code 1\nError: Cannot find module 'express'",
"is_interrupt": false,
"duration_ms": 4187
}
| 欄位 | 說明 |
|---|---|
error |
描述錯誤內容的字串。格式取決於失敗的工具 |
is_interrupt |
選用布林值。當失敗是以中止的形式傳到 Claude Code,而非工具回報的錯誤時為 true。取消正在執行的工具不會觸發此 hook;工具結果會改為帶有中斷訊息 |
duration_ms |
選用。工具執行時間(毫秒)。不包含花在權限提示與 PreToolUse hook 上的時間 |
error 字串通常與 Claude 收到的失敗工具結果文字相同。其格式因工具與失敗情況而異。請讓您的 hook 依據 tool_name、is_interrupt 以及第一行的 Exit code N 判斷;字串的其餘部分請視為顯示用文字,而非穩定的格式。
- 對於 Bash 與 PowerShell,已執行並結束的命令會產生第一行
Exit code N,接著是命令產生的任何輸出,以 stdout 與 stderr 交錯的單一區塊呈現 - 當 Claude Code 無法啟動 shell 程序本身時,payload 也可能只帶有單純的失敗訊息,沒有退出碼那一行
- Claude Code 會在
... [N characters truncated] ...標記周圍截斷長字串的中間部分,也可能插入自己的文字行,例如Command timed out after 2m 0s
PostToolUseFailure 決策控制
PostToolUseFailure hook 可以在工具失敗後向 Claude 提供上下文。除了所有 hook 皆可使用的 JSON 輸出欄位之外,您的 hook 指令碼還可以傳回以下事件專屬欄位:
| 欄位 | 說明 |
|---|---|
additionalContext |
與錯誤一併加入 Claude 上下文的字串。請參閱為 Claude 新增上下文 |
{
"hookSpecificOutput": {
"hookEventName": "PostToolUseFailure",
"additionalContext": "Additional information about the failure for Claude"
}
}
PostToolBatch
在批次中的每個工具呼叫都已解決後執行一次,時間點在 Claude Code 將下一個請求傳送給模型之前。PostToolUse 會針對每個工具觸發一次,這表示當 Claude 進行平行工具呼叫時,它會同時觸發。PostToolBatch 則會帶著完整批次恰好觸發一次,因此適合用來注入取決於已執行工具集合、而非任何單一工具的上下文。此事件沒有 matcher。
PostToolBatch 輸入
除了通用輸入欄位之外,PostToolBatch hook 還會收到 tool_calls,這是描述批次中每個工具呼叫的陣列:
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolBatch",
"tool_calls": [
{
"tool_name": "Read",
"tool_input": {"file_path": "/.../ledger/accounts.py"},
"tool_use_id": "toolu_01...",
"tool_response": "1\tfrom __future__ import annotations\n2\t..."
},
{
"tool_name": "Read",
"tool_input": {"file_path": "/.../ledger/transactions.py"},
"tool_use_id": "toolu_02...",
"tool_response": "1\tfrom __future__ import annotations\n2\t..."
}
]
}
tool_response 包含與模型在對應 tool_result 區塊中收到的相同內容。其值為序列化字串或內容區塊陣列,與工具輸出時完全相同。對於 Read,這表示是帶有行號前綴的文字,而非原始檔案內容。回應可能很大,因此請只解析您需要的欄位。
tool_response 的結構與 PostToolUse 的不同。PostToolUse 傳遞的是工具的結構化 Output 物件,例如 Write 的 {filePath: "...", type: "create"};PostToolBatch 傳遞的則是模型看到的序列化 tool_result 內容。
PostToolBatch 決策控制
PostToolBatch hook 可以為 Claude 注入上下文。除了所有 hook 皆可使用的 JSON 輸出欄位之外,您的 hook 指令碼還可以傳回以下事件專屬欄位:
| 欄位 | 說明 |
|---|---|
additionalContext |
在下一次模型呼叫之前注入一次的上下文字串。關於傳遞細節、應放入的內容,以及恢復的工作階段如何處理過去的值,請參閱為 Claude 新增上下文 |
{
"hookSpecificOutput": {
"hookEventName": "PostToolBatch",
"additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
}
}
傳回 decision: "block" 或 continue: false 會在下一次模型呼叫之前停止代理式迴圈。封鎖訊息來自 JSON 的 reason 或 stopReason,或在退出碼 2 時來自 stderr。您會在逐字稿中看到它以警告形式呈現,且它會保留在對話中,因此對話繼續時 Claude 會看到它。
PermissionDenied
當自動模式拒絕工具呼叫時執行,包括因與自動模式分開的安全檢查拒絕了分類器本身的請求或其回應無法解析,而在沒有分類器判定的情況下拒絕時。此 hook 只在自動模式下觸發:當您手動拒絕權限對話框、PreToolUse hook 封鎖呼叫,或 deny 規則相符時,它都不會執行。可用來記錄拒絕情況、調整設定,或告知模型可以重試該工具呼叫。
依工具名稱比對,可用值與 PreToolUse 相同。
PermissionDenied 輸入
除了通用輸入欄位之外,PermissionDenied hook 還會收到 tool_name、tool_input、tool_use_id 與 reason。對於 MCP 工具,它們也會收到 mcp_server 物件。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "auto",
"hook_event_name": "PermissionDenied",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf /tmp/build",
"description": "Clean build directory"
},
"tool_use_id": "toolu_01ABC123...",
"reason": "[Irreversible Local Destruction]"
}
| 欄位 | 說明 |
|---|---|
reason |
拒絕原因。對於分類器判定,在大多數工作階段中,它會以方括號標示相符的規則,例如 [Data Exfiltration];其他形式請參閱檢閱拒絕。對於無判定的拒絕,它以 Auto mode could not evaluate this action and is blocking it for safety 開頭。對於因分類器模型無法使用而造成的拒絕,它是固定文字 Classifier unavailable |
PermissionDenied 決策控制
PermissionDenied hook 可以告知模型它可以重試遭拒的工具呼叫。傳回將 hookSpecificOutput.retry 設為 true 的 JSON 物件:
{
"hookSpecificOutput": {
"hookEventName": "PermissionDenied",
"retry": true
}
}
當 retry 為 true 時,Claude Code 會在對話中加入一則訊息,告知模型可以重試該工具呼叫。Claude Code 本身不會撤銷拒絕。如果您的 hook 未傳回 JSON,或傳回 retry: false,拒絕將維持不變,模型會收到原始的拒絕訊息。
當分類器未對該動作做出判定時,Claude Code 會忽略 retry: true:即其回應無法解析,或與自動模式分開的安全檢查拒絕了分類器本身的請求。對於這些拒絕,Claude Code 已會在拒絕訊息中告知模型應稍後重試或繼續進行其他工作。
Notification
在 Claude Code 傳送通知時執行。依通知類型比對。省略 matcher 即可針對所有通知類型執行 hook。
即使關閉桌面通知,您仍會收到這些 hook 事件:preferredNotifChannel 設定(包括 notifications_disabled)只會改變提醒您的方式,不會影響您的 hook 是否執行。
| Matcher | 觸發時機 |
|---|---|
permission_prompt |
Claude 需要您核准工具使用或沙箱命令的網路請求,且提示已等待約六秒 |
idle_prompt |
Claude 約在 60 秒前完成回應,且您此後未曾輸入 |
auth_success |
身分驗證完成 |
elicitation_dialog |
MCP 伺服器開啟 elicitation 表單,且您約六秒未輸入 |
elicitation_url_dialog |
MCP 伺服器要求您開啟瀏覽器 URL,且您約六秒未輸入 |
elicitation_complete |
MCP 伺服器回報 URL 模式 elicitation 已完成 |
elicitation_response |
MCP elicitation 回應已傳回伺服器 |
agent_needs_input |
當 agent view 在終端機中開啟時,背景工作階段開始等待您的輸入。當終端機工作階段向您顯示 agent team 隊員的終端機設定問題或自動模式關於分類器請求費用的通知,且您約六秒未輸入時,也會觸發 |
agent_completed |
背景工作階段完成或失敗。僅在 agent view 於終端機中開啟時觸發 |
quota_auto_resume_fired |
Claude Code 在 claude.ai 用量上限暫停您的任務後繼續執行:於重設時,或當您在等待期間於 Claude Code 中執行的某些操作(例如新增用量點數、升級方案或切換模型)使用量再次可用時提前繼續,但有模型設定例外 |
quota_auto_resume_stale |
claude.ai 用量上限在您的電腦休眠超過約 30 分鐘期間重設。Claude Code 會等待您按下 Enter,而不是自動繼續。若休眠時間較短,則會繼續並改為觸發 quota_auto_resume_fired |
quota_auto_resume_disabled |
Claude Code 結束對 claude.ai 用量上限的等待,但未繼續您的任務:autoContinueAtUsageLimit 已關閉,或在 Claude Code 自行開始的等待期間重設時間延後超過 24 小時、繼續的任務持續觸及上限,或繼續操作在抵達模型前遭到封鎖。當您按下 Esc 或 Ctrl+C,或選擇 Don't continue automatically 時不會觸發 |
quota_auto_resume_fired、quota_auto_resume_stale 與 quota_auto_resume_disabled 類型需要 Claude Code v2.1.234 或更新版本。
在終端機工作階段中,針對沙箱命令網路請求的 permission_prompt 需要 Claude Code v2.1.246 或更新版本。
針對隊員終端機設定問題的 agent_needs_input 需要 Claude Code v2.1.248 或更新版本。
permission_prompt、idle_prompt、elicitation_dialog 與 elicitation_url_dialog 類型與桌面通知共用相同的計時方式,因此在終端機工作階段中,只有當您看起來已離開終端機時才會看到它們:
- 當您約六秒未輸入時,預期會出現
permission_prompt。計時器在權限提示出現時開始,每次按鍵都會使其延後。若要在 Claude 要求使用工具的權限時立即執行 hook,請改用 PermissionRequest。 - 預期
idle_prompt會在 Claude 完成回應約 60 秒後出現,且僅在您此後未曾輸入,並且沒有背景 agent(例如背景 subagent)仍在執行時才會出現。Claude Code 在等待 claude.ai 用量上限重設期間不會傳送idle_prompt。當等待自行結束時,會改為觸發其中一種quota_auto_resume_*類型。 - 當您約六秒未輸入時,預期會出現
elicitation_dialog(針對 elicitation 表單)或elicitation_url_dialog(針對瀏覽器 URL 請求)。兩者與permission_prompt共用相同的六秒門檻:計時器在對話框出現時開始,每次按鍵都會使其延後。
在另一個對話框顯示於畫面上時抵達的權限請求或 elicitation,會維持相同的六秒門檻,並從請求抵達時開始計時。即使該請求仍在已開啟的對話框後方等待,其通知也可能送達您。
在 Claude Code 將權限請求傳送給 Agent SDK 的 canUseTool 回呼的工作階段中(Claude Desktop 與 VS Code 擴充功能即以此方式託管 Claude Code),Claude Code 對 permission_prompt 的計時方式有所不同:
- 預期
permission_prompt會在 Claude 要求權限約六秒後出現。Claude Code 不會因您輸入而延後它。 - 如果您或 PermissionRequest hook 較早回應,Claude Code 不會執行
permission_prompt。 - 將
CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS設為1,即可在這些工作階段中關閉permission_prompt。
在 v2.1.233 之前,permission_prompt 不會在這些工作階段中觸發。
使用不同的 matcher,即可依通知類型執行不同的處理常式。此設定會在 Claude 需要權限核准時觸發權限專用的警示指令碼,並在 Claude 閒置時觸發另一個通知:
{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "/path/to/permission-alert.sh"
}
]
},
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "/path/to/idle-notification.sh"
}
]
}
]
}
}
Notification 輸入
除了通用輸入欄位之外,Notification hook 還會收到含有通知文字的 message、選用的 title,以及指出觸發類型的 notification_type。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Notification",
"message": "Claude needs your permission",
"title": "Permission needed",
"notification_type": "permission_prompt"
}
Notification hook 無法封鎖或修改通知。Claude Code 會捨棄其 systemMessage 與 continue 欄位,但仍會輸出 terminalSequence,桌面通知範例即仰賴此欄位。Notification hook 的用途是執行副作用,例如將通知轉送至外部服務。
SubagentStart
當 Claude 使用 Agent 工具產生 subagent、當 Claude 恢復 subagent,以及每當行程內 agent team 隊員處理新訊息時執行。支援以 matcher 依 agent 類型名稱篩選。對於內建 agent,這是 agent 名稱,例如 general-purpose、Explore 或 Plan。對於自訂 subagent,這是 agent frontmatter 中的 name 欄位,而非檔案名稱。
對於由外掛提供的 subagent,agent 類型是外掛範圍的識別碼,例如 my-plugin:reviewer,而非單純的 frontmatter 名稱。冒號會使外掛範圍的名稱走正規表示式路徑,因此請以 ^ 與 $ 錨定 matcher 以進行完全比對:^my-plugin:reviewer$。
SubagentStart 輸入
除了通用輸入欄位之外,SubagentStart hook 還會收到含有 subagent 唯一識別碼的 agent_id,以及含有 matcher 篩選所依據之 agent 名稱的 agent_type。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SubagentStart",
"agent_id": "agent-abc123",
"agent_type": "Explore"
}
SubagentStart hook 無法封鎖 subagent 的建立,但可以將上下文注入 subagent。除了所有 hook 皆可使用的 JSON 輸出欄位之外,您還可以傳回:
| 欄位 | 說明 |
|---|---|
additionalContext |
在 subagent 對話開始時、其第一個提示詞之前加入其上下文的字串。請參閱為 Claude 新增上下文 |
{
"hookSpecificOutput": {
"hookEventName": "SubagentStart",
"additionalContext": "Follow security guidelines for this task"
}
}
當 hook 針對同一個 subagent 再次執行時,Claude Code 只會在 subagent 的上下文中尚未保有先前執行所注入的副本時,才注入傳回的上下文。啟動時注入的副本會保留在原處,使 subagent 的提示快取維持完整。在自動壓縮捨棄該副本後,Claude Code 會再次注入下一次執行的上下文。
SubagentStop
在 Claude Code subagent 完成回應時執行。依 agent 類型比對,可用值與 SubagentStart 相同。
SubagentStop 輸入
除了通用輸入欄位之外,SubagentStop hook 還會收到 stop_hook_active、agent_id、agent_type、agent_transcript_path 與 last_assistant_message。agent_type 欄位是用於 matcher 篩選的值。transcript_path 是主工作階段的逐字稿,而 agent_transcript_path 則是 subagent 自己的逐字稿,儲存在巢狀的 subagents/ 資料夾中。last_assistant_message 欄位包含 subagent 最終回應的文字內容,因此 hook 無需解析逐字稿檔案即可存取它。
並非每個 SubagentStop 事件都來自 Claude 產生的 subagent。Claude Code 也會為其部分功能執行內部 agent,例如提示詞建議與 /btw 旁支問題,這些 agent 完成時同樣會觸發 SubagentStop。對於這些事件,agent_type 是工作階段本身所執行的 agent 名稱,例如以 --agent 或 agent 設定指定的名稱;若工作階段未指定任何 agent,則為空字串。
指定 agent 類型的 matcher 不會比對到空的 agent_type。matcher 省略、為 "" 或 "*",或為可比對空字串之正規表示式的 hook,也會針對 agent_type 為空的事件執行。
在 Claude Code v2.1.271 或更新版本中,搭配 SubagentHandback 工具執行的 subagent 會在停止前透過該工具傳遞其報告。此時 last_assistant_message 欄位保存的是 subagent 的結尾文字(若有),而非所傳遞的報告。報告是該次呼叫的 message 輸入,比對 SubagentHandback 的 PreToolUse 或 PostToolUse hook 會以 tool_input.message 收到它。
SubagentStop hook 也會收到 Stop 輸入中所述的 background_tasks 與 session_crons 陣列。這兩個陣列的範圍都是父工作階段,而非 subagent。
{
"session_id": "abc123",
"transcript_path": "~/.claude/projects/.../abc123.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "SubagentStop",
"stop_hook_active": false,
"agent_id": "def456",
"agent_type": "Explore",
"agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
"last_assistant_message": "Analysis complete. Found 3 potential issues...",
"background_tasks": [],
"session_crons": []
}
SubagentStop hook 使用與 Stop hook 相同的決策控制格式,包括將 hookEventName 設為 "SubagentStop" 的 hookSpecificOutput.additionalContext,用於提供讓 subagent 繼續執行的非錯誤回饋。傳回附有 reason 的 decision: "block" 會讓 subagent 繼續執行,並將 reason 作為其下一個指令傳遞給 subagent。以退出碼 2 封鎖的 hook 也會以相同方式傳遞其 stderr 訊息。若要在 subagent 返回後將上下文注入父工作階段,請改用針對 Agent 工具的 PostToolUse hook。
TaskCreated
在透過 TaskCreate 工具建立任務時執行。可用來強制執行命名慣例、要求任務描述,或阻止特定任務被建立。在不含 Task 工具的工作階段中,此事件不會觸發。
TaskCreated hook 不支援 matcher,每次發生時都會觸發。
TaskCreated 輸入
除了通用輸入欄位之外,TaskCreated hook 還會收到 task_id、task_subject,以及選用的 task_description、teammate_name 與 team_name。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "TaskCreated",
"task_id": "task-001",
"task_subject": "Implement user authentication",
"task_description": "Add login and signup endpoints",
"teammate_name": "implementer",
"team_name": "session-a1b2c3d4"
}
| 欄位 | 說明 |
|---|---|
task_id |
正在建立之任務的識別碼 |
task_subject |
任務標題 |
task_description |
任務的詳細描述。可能不存在 |
teammate_name |
建立任務之隊員的名稱。可能不存在 |
team_name |
已棄用。由工作階段衍生的團隊名稱;將在未來版本中移除 |
TaskCreated 決策控制
TaskCreated hook 可以透過兩種方式封鎖建立。無論哪種方式,Claude Code 都會刪除該任務,並將您的訊息作為工具錯誤傳回給 Claude。Claude Code 會忽略此事件的 continue: false,Claude 會繼續工作。
- 退出碼 2:Claude Code 將 stderr 文字作為訊息傳回。
- JSON
{"decision": "block", "reason": "..."}:Claude Code 將reason作為訊息傳回。
此範例會封鎖主旨不符合所需格式的任務:
#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
exit 2
fi
exit 0
TaskCompleted
在任務即將被標記為已完成時執行。這會在兩種情況下觸發:任何 agent 透過 TaskUpdate 工具明確將任務標記為已完成時,或 agent team 隊員在仍有進行中任務的情況下結束其回合時。可用來在任務關閉前強制執行完成條件,例如通過測試或 lint 檢查。
TaskCompleted hook 不支援 matcher,每次發生時都會觸發。
TaskCompleted 輸入
除了通用輸入欄位之外,TaskCompleted hook 還會收到 task_id、task_subject,以及選用的 task_description、teammate_name 與 team_name。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "TaskCompleted",
"task_id": "task-001",
"task_subject": "Implement user authentication",
"task_description": "Add login and signup endpoints",
"teammate_name": "implementer",
"team_name": "session-a1b2c3d4"
}
| 欄位 | 說明 |
|---|---|
task_id |
正在完成之任務的識別碼 |
task_subject |
任務標題 |
task_description |
任務的詳細描述。可能不存在 |
teammate_name |
完成任務之隊員的名稱。可能不存在 |
team_name |
已棄用。由工作階段衍生的團隊名稱;將在未來版本中移除 |
TaskCompleted 決策控制
TaskCompleted hook 支援兩種控制任務完成的方式:
- 退出碼 2:任務不會被標記為已完成,stderr 訊息會作為回饋傳回給模型。
- JSON
{"continue": false, "stopReason": "..."}:當事件是由隊員結束其回合所觸發時,會完全停止該隊員,與Stophook 的行為一致。stopReason會顯示給使用者。當事件是由TaskUpdate工具觸發時,Claude Code 會忽略continue: false;退出碼 2 仍會封鎖完成。
此範例會執行測試,並在測試失敗時封鎖任務完成:
#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
# Run the test suite
if ! npm test 2>&1; then
echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
exit 2
fi
exit 0
Stop
在主要 Claude Code agent 完成回應時執行。若停止是由使用者中斷所造成,則不會執行。API 錯誤會改為觸發 StopFailure。
/goal 命令是工作階段範圍、以提示詞為基礎之 Stop hook 的內建捷徑。當您希望 Claude 持續朝某個條件努力,而不想撰寫 hook 設定時,可以使用它。
Stop 輸入
除了通用輸入欄位之外,Stop hook 還會收到 stop_hook_active、last_assistant_message、background_tasks 與 session_crons。當 Claude Code 已因 stop hook 而繼續執行時,stop_hook_active 欄位為 true。請檢查此值或處理逐字稿,以避免因永遠無法解決的條件而持續封鎖。Claude Code 套用連續 8 次繼續的上限:在 stop hook 連續讓回合繼續八次後,Claude Code 會覆寫下一次封鎖並結束回合。若要提高上限,請設定 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP。
last_assistant_message 欄位包含 Claude 最終回應的文字內容,因此 hook 無需解析逐字稿檔案即可存取它。對於針對剛完成之回合採取動作的 hook(例如朗讀或通知 hook),請使用此欄位,而非讀取 transcript_path:並非所有版本都保證逐字稿檔案在 Stop 時已包含最終訊息。
background_tasks 與 session_crons 陣列讓 hook 能夠區分「工作階段已完成」與「工作階段已暫停,等待背景工作將其喚醒」。當任務登錄可存取時,兩個陣列都會存在;若沒有正在進行或已排程的項目,則為空陣列。
background_tasks 中的每個項目描述一個正在進行的任務,並使用以下欄位:
| 欄位 | 說明 |
|---|---|
id |
任務識別碼 |
type |
易讀的任務類型標籤,例如 shell、subagent、monitor、workflow、teammate、cloud session 或 MCP task。每個標籤識別建立該任務的 Claude Code 功能。對於無法辨識的類型,會退回使用原始判別值 |
status |
目前的任務狀態 |
description |
自由文字描述,上限為 1000 個字元,截斷時字串中會帶有 … [+N chars] 標記 |
command |
shell 命令列,上限為 1000 個字元。僅存在於 shell 任務 |
agent_type |
subagent 類型名稱。僅存在於 subagent 任務 |
server |
MCP 伺服器名稱。僅存在於 monitor 與 MCP task 任務 |
tool |
MCP 工具名稱。僅存在於 monitor 與 MCP task 任務 |
name |
工作流程名稱。僅存在於 workflow 任務 |
session_crons 中的每個項目描述一個工作階段範圍的排程喚醒,來源為 CronCreate、ScheduleWakeup 與 /loop:
| 欄位 | 說明 |
|---|---|
id |
Cron 任務識別碼 |
schedule |
Cron 運算式,例如 0 9 * * 1-5 |
recurring |
對於排程只編碼單一觸發時間的一次性喚醒為 false,對於每次相符時都會再次觸發的任務為 true |
prompt |
cron 觸發時送出的提示詞,上限為 1000 個字元,並帶有相同的 … [+N chars] 標記 |
此範例顯示含有一個正在進行之 shell 任務與一個週期性 cron 的 Stop 輸入:
{
"session_id": "abc123",
"transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "Stop",
"stop_hook_active": true,
"last_assistant_message": "I've completed the refactoring. Here's a summary...",
"background_tasks": [
{
"id": "task-001",
"type": "shell",
"status": "running",
"description": "tail logs",
"command": "tail -f /var/log/syslog"
}
],
"session_crons": [
{
"id": "cron-001",
"schedule": "0 9 * * 1-5",
"recurring": true,
"prompt": "check the build"
}
]
}
Stop 決策控制
Stop 與 SubagentStop hook 可以控制 Claude 是否繼續。除了所有 hook 皆可使用的 JSON 輸出欄位之外,您的 hook 指令碼還可以傳回以下事件專屬欄位:
| 欄位 | 說明 |
|---|---|
decision |
"block" 會阻止 Claude 停止。省略即允許 Claude 停止 |
reason |
當 decision 為 "block" 時為必填。告訴 Claude 為何應繼續 |
hookSpecificOutput.additionalContext |
提供給 Claude 的非錯誤回饋。對話會繼續,讓 Claude 能據以行動,但與 decision: "block" 不同,它在逐字稿中會顯示為 hook 回饋,而非 hook 錯誤 |
以退出碼 2 封鎖的 hook,其處理方式與 reason 相同:Claude 會收到 stderr 訊息,作為它應繼續的原因說明。
{
"decision": "block",
"reason": "Must be provided when Claude is blocked from stopping"
}
當 hook 依設計運作並為 Claude 提供指引時(例如「完成前請執行測試套件」),請使用 additionalContext。它會透過與 decision: "block" 相同的迴圈保護機制讓對話繼續,即 stop_hook_active 輸入與連續 8 次繼續的上限,但逐字稿會將其標示為 Stop hook feedback,且不會顯示 hook 錯誤通知:
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "Please run the test suite before finishing"
}
}
StopFailure
當回合因 API 錯誤而結束時,取代 Stop 執行。除了 terminalSequence 之外,Claude Code 會忽略此 hook 的輸出與退出碼。當 Claude 因速率限制、身分驗證問題或其他 API 錯誤而無法完成回應時,可用來記錄失敗、傳送警示或採取復原動作。
StopFailure 輸入
除了通用輸入欄位之外,StopFailure hook 還會收到 error、選用的 error_details 與選用的 last_assistant_message。error 欄位識別錯誤類型,並用於 matcher 篩選。
| 欄位 | 說明 |
|---|---|
error |
錯誤類型:rate_limit、overloaded、authentication_failed、oauth_org_not_allowed、account_on_hold、billing_error、invalid_request、model_not_found、server_error、max_output_tokens、cloud_credential_error 或 unknown |
error_details |
關於錯誤的其他細節(若有) |
last_assistant_message |
對話中顯示的錯誤文字。與 Stop 和 SubagentStop 中此欄位保存 Claude 對話輸出的情況不同,對於 StopFailure,它包含 API 錯誤字串本身,例如 "API Error: Rate limit reached" |
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "StopFailure",
"error": "rate_limit",
"error_details": "429 Too Many Requests",
"last_assistant_message": "API Error: Rate limit reached"
}
StopFailure hook 沒有決策控制。它們僅用於通知與記錄日誌。
TeammateIdle
當 agent team 隊員在結束其回合後即將進入閒置狀態時執行。可用來在隊員停止工作前強制執行品質關卡,例如要求通過 lint 檢查或確認輸出檔案存在。
TeammateIdle hook 不支援 matcher,每次發生時都會觸發。
TeammateIdle 輸入
除了通用輸入欄位之外,TeammateIdle hook 還會收到 teammate_name 與 team_name。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "TeammateIdle",
"teammate_name": "researcher",
"team_name": "session-a1b2c3d4"
}
| 欄位 | 說明 |
|---|---|
teammate_name |
即將進入閒置狀態之隊員的名稱 |
team_name |
已棄用。由工作階段衍生的團隊名稱;將在未來版本中移除 |
TeammateIdle 決策控制
TeammateIdle hook 支援兩種控制隊員行為的方式:
- 退出碼 2:隊員會收到 stderr 訊息作為回饋,並繼續工作而不進入閒置狀態。
- JSON
{"continue": false, "stopReason": "..."}:完全停止該隊員,與Stophook 的行為一致。stopReason會顯示給使用者。
此範例會在允許隊員進入閒置狀態前,檢查建置產物是否存在:
#!/bin/bash
if [ ! -f "./dist/output.js" ]; then
echo "Build artifact missing. Run the build before stopping." >&2
exit 2
fi
exit 0
ConfigChange
在工作階段期間設定檔變更時執行。可用來稽核設定變更、強制執行安全政策,或封鎖對設定檔未經授權的修改。
當設定檔、受管政策檔案或 skill 檔案變更時,Claude Code 會執行 ConfigChange hook。對於受管政策,只有在 managed-settings.json 或 managed-settings.d/ 中的檔案變更時才會執行。套用伺服器管理設定以及 macOS 受管偏好設定或 Windows 登錄政策的變更時,不會執行這些 hook。在啟用 wslInheritsWindowsSettings 的 WSL 上,它在政策輪詢時套用已變更的 Windows 端受管設定檔,同樣不會執行這些 hook。
matcher 依設定來源篩選:
| Matcher | 觸發時機 |
|---|---|
user_settings |
~/.claude/settings.json 變更 |
project_settings |
.claude/settings.json 變更 |
local_settings |
.claude/settings.local.json 變更 |
policy_settings |
managed-settings.json 或 managed-settings.d/ 中的檔案變更 |
skills |
.claude/skills/ 中的 skill 檔案變更 |
此範例會記錄所有設定變更以供安全稽核:
{
"hooks": {
"ConfigChange": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/audit-config-change.sh",
"args": []
}
]
}
]
}
}
ConfigChange 輸入
除了通用輸入欄位之外,ConfigChange hook 還會收到 source 與選用的 file_path。source 欄位指出哪種設定類型發生變更,file_path 則提供被修改之特定檔案的路徑。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "ConfigChange",
"source": "project_settings",
"file_path": "/Users/.../my-project/.claude/settings.json"
}
ConfigChange 決策控制
ConfigChange hook 可以封鎖設定變更使其不生效。使用退出碼 2 或 JSON decision 即可阻止變更。遭封鎖時,新設定不會套用至正在執行的工作階段。
| 欄位 | 說明 |
|---|---|
decision |
"block" 會阻止套用設定變更。省略即允許變更 |
reason |
會被接受,但永遠不會顯示 |
{
"decision": "block",
"reason": "Configuration changes to project settings require admin approval"
}
policy_settings 變更無法被封鎖。當機器上的受管設定檔變更時,hook 仍會針對 policy_settings 來源觸發,因此您可以用它們記錄這些編輯,但任何封鎖決策都會被忽略。這可確保企業受管設定一律生效。當伺服器管理設定抵達或重新整理時,Claude Code 不會執行 ConfigChange hook。
Claude Code 會依據 ConfigChange hook JSON 輸出中的封鎖決策採取行動,並捨棄 systemMessage 與 continue。無論您是以 reason 還是以退出碼 2 的 stderr 封鎖,遭封鎖的變更都不會向您或 Claude 顯示任何訊息。Claude Code 只會在偵錯日誌中寫入一行。
CwdChanged
當主要對話中的 shell 命令變更工作目錄時執行,例如 Claude 執行 cd 命令時。可用來回應目錄變更:重新載入環境變數、啟用專案專屬的工具鏈,或自動執行設定指令碼。可與 FileChanged 搭配,用於像 direnv 這類管理各目錄環境的工具。
CwdChanged hook 可以存取 CLAUDE_ENV_FILE。寫入該檔案的變數會保留到後續的 Bash 命令中,直到下一個 CwdChanged 事件時由 Claude Code 清除。
CwdChanged 不支援 matcher,每次發生時都會觸發。
CwdChanged 輸入
除了通用輸入欄位之外,CwdChanged hook 還會收到 old_cwd 與 new_cwd。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project/src",
"hook_event_name": "CwdChanged",
"old_cwd": "/Users/my-project",
"new_cwd": "/Users/my-project/src"
}
CwdChanged 輸出
除了所有 hook 皆可使用的 JSON 輸出欄位之外,CwdChanged hook 還可以傳回 watchPaths,以動態設定 FileChanged 監看的檔案路徑:
| 欄位 | 說明 |
|---|---|
watchPaths |
絕對路徑陣列。取代目前的動態監看清單。來自您 matcher 設定的路徑一律會被監看。傳回空陣列會清除動態清單,這在進入新目錄時很常見 |
CwdChanged hook 沒有決策控制。它們無法封鎖目錄變更。
Claude Code 會從其 JSON 輸出讀取 watchPaths 與 systemMessage,並捨棄 continue。在互動式工作階段中,它會將 systemMessage 顯示為簡短的終端機通知。該訊息不會傳到 SDK 訊息串流。
DirectoryAdded
在您於工作階段中途使用 /add-dir 命令新增工作目錄後,或在 SDK 用戶端以 register_repo_root 控制請求新增工作目錄後執行。可用來準備新加入的儲存庫,例如安裝其相依套件。
在下列情況下,Claude Code 不會觸發此事件:
- 您以
--add-dir啟動旗標傳入目錄;這些目錄由 SessionStart 涵蓋 - 您在
/permissions的 Workspace 分頁中新增目錄 - 您新增的目錄已是工作目錄或位於某個工作目錄內
Claude Code 會在重新整理沙箱與權限狀態後觸發 DirectoryAdded,因此當您的 hook 執行時,沙箱化的工具已能看到新目錄。hook 命令本身則在沙箱外執行。
Claude Code 不會等待 hook:新增會立即完成,hook 則在背景執行,使用 600 秒的預設逾時。
matcher 依目錄的新增方式篩選:
| Matcher | 觸發時機 |
|---|---|
slash_command |
您以 /add-dir 新增目錄 |
register_repo_root |
SDK 用戶端以 register_repo_root 控制請求新增目錄 |
DirectoryAdded 輸入
除了通用輸入欄位之外,DirectoryAdded hook 還會收到 directory 與 source。
| 欄位 | 說明 |
|---|---|
directory |
所新增目錄的絕對路徑 |
source |
目錄的新增方式,/add-dir 為 "slash_command",SDK 控制請求為 "register_repo_root" |
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "DirectoryAdded",
"directory": "/Users/my-other-repo",
"source": "slash_command"
}
DirectoryAdded hook 沒有決策控制。它們無法封鎖新增,因為 hook 執行時新增已經完成。Claude Code 會捨棄其 JSON 輸出中的 continue 欄位,其餘部分則依來源以不同方式呈現:
slash_command:Claude Code 會在下一個對話回合將 hook 的systemMessage作為上下文傳遞給 Claude,而不是顯示給您。失敗 hook 的數量會出現在逐字稿中。完整的失敗輸出會寫入偵錯日誌register_repo_root:Claude Code 只會將systemMessage輸出與失敗輸出寫入偵錯日誌
FileChanged
當受監看的檔案在磁碟上變更時執行。Claude Code 是以檔案系統監看器偵測變更,而非檢查工具呼叫,因此無論是什麼變更了檔案,它都會執行 hook:Edit 或 Write 工具呼叫、Claude 以 Bash 執行的指令碼,或完全在 Claude Code 之外的程序。常見用途是在專案設定檔變更時重新載入環境變數。
此事件的 matcher 有兩個作用:
- 建立監看清單:其值會以
|分割,每個片段都會被註冊為工作目錄中的字面檔案名稱,因此".envrc|.env"會精確監看這兩個檔案。正規表示式模式在此沒有用處:像^\.env這樣的值會監看名稱字面上就是^\.env的檔案。 - 篩選要執行的 hook:當受監看的檔案變更時,同一個值會依標準的 matcher 規則,對變更檔案的基本名稱篩選要執行哪些 hook 群組。
此範例會在任何變更後正規化 data.csv 的行尾,包括 Bash 命令或外部指令碼改寫該檔案的情況:
{
"hooks": {
"FileChanged": [
{
"matcher": "data.csv",
"hooks": [
{
"type": "command",
"command": "/path/to/normalize-line-endings.sh"
}
]
}
]
}
}
hook 會從 stdin 上 JSON 輸入的 file_path 欄位讀取變更檔案的絕對路徑。其 grep 防護條件檢查的正是 perl 要移除的內容,也就是行尾的 CR,因此正規化之後的那次執行會直接結束而不動到檔案。較寬鬆的防護條件會造成無限迴圈,因為即使沒有替換任何內容,perl -i 仍會改寫檔案,而 Claude Code 會在每次改寫後再次執行 hook。請將此指令碼儲存於 /path/to/normalize-line-endings.sh 並設為可執行:
#!/bin/bash
FILE=$(jq -r .file_path)
if grep -q $'\r$' "$FILE"; then
perl -pi -e 's/\r$//' "$FILE"
fi
若要確認 hook 正常運作,請要求 Claude 以 Bash 命令在 data.csv 後附加一行 CRLF。Claude Code 會執行 hook,檔案最終會使用 LF 行尾。
若要監看無法事先命名的檔案,請從 hook 傳回 watchPaths 以動態更新監看清單。Claude Code 只有在某處指定了要監看的檔案時才會啟動監看器,因此請以 matcher 至少指定一個檔案的 FileChanged 群組,或以傳回 watchPaths 的 SessionStart 或 CwdChanged hook 來初始化清單。當受監看的檔案變更時,matcher 仍會篩選要執行哪些 hook 群組,因此請讓處理動態路徑的群組省略 matcher,這樣會比對每個受監看的檔案,且不會在監看清單中新增任何項目。"*" matcher 也會比對每個檔案,但 Claude Code 會像處理其他值一樣,將其作為名為 * 的字面檔案註冊到監看清單中。
FileChanged hook 可以存取 CLAUDE_ENV_FILE。寫入該檔案的變數會保留到後續的 Bash 命令中,直到下一個 CwdChanged 事件時由 Claude Code 清除。
FileChanged 輸入
除了通用輸入欄位之外,FileChanged hook 還會收到 file_path 與 event。
| 欄位 | 說明 |
|---|---|
file_path |
變更檔案的絕對路徑 |
event |
發生的事件:修改檔案為 "change",建立檔案為 "add",刪除檔案為 "unlink" |
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "FileChanged",
"file_path": "/Users/my-project/.envrc",
"event": "change"
}
FileChanged 輸出
除了所有 hook 皆可使用的 JSON 輸出欄位之外,FileChanged hook 還可以傳回 watchPaths,以動態更新要監看的檔案路徑:
| 欄位 | 說明 |
|---|---|
watchPaths |
絕對路徑陣列。取代目前的動態監看清單。來自您 matcher 設定的路徑一律會被監看。當您的 hook 指令碼依據變更的檔案發現其他需要監看的檔案時,請使用此欄位 |
FileChanged hook 沒有決策控制。它們無法阻止檔案變更發生。
Claude Code 會從其 JSON 輸出讀取 watchPaths 與 systemMessage,並捨棄 continue。在互動式工作階段中,它會將 systemMessage 顯示為簡短的終端機通知。該訊息不會傳到 SDK 訊息串流。
WorktreeCreate
在建立 worktree 時執行,無論是來自 claude --worktree、來自使用 isolation: "worktree" 的 subagent,或是為 Claude Code 隔離在其自身 worktree 中的背景工作階段。依預設,Claude Code 會以 git worktree 建立隔離的工作副本。設定 WorktreeCreate hook 會取代該預設的 git 行為,讓您能使用其他版本控制系統,例如 SVN、Perforce 或 Mercurial。
由於 hook 會完全取代預設行為,因此不會處理 .worktreeinclude。若您需要將 .env 之類的本機設定檔複製到新的 worktree,請在您的 hook 指令碼中進行。
hook 必須傳回所建立之 worktree 目錄的路徑。Claude Code 會將此路徑作為隔離工作階段的工作目錄。關於各 hook 類型如何傳回路徑,請參閱 WorktreeCreate 輸出。
Claude Code 會依據 hook 是否成功以及傳回的路徑採取行動,並捨棄 systemMessage 與 continue。
此範例會建立 SVN 工作副本,並印出路徑供 Claude Code 使用。請將儲存庫 URL 替換為您自己的:
{
"hooks": {
"WorktreeCreate": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
}
]
}
]
}
}
hook 會從 stdin 上的 JSON 輸入讀取 worktree 的 name,將全新副本簽出到新目錄,並印出目錄路徑。最後一行的 echo 就是 Claude Code 讀取為 worktree 路徑的內容。請將其他任何輸出重新導向至 stderr,以免干擾路徑。
WorktreeCreate 輸入
除了通用輸入欄位之外,WorktreeCreate hook 還會收到 name 欄位。這是新 worktree 的 slug 識別碼,由使用者指定或自動產生,例如 bold-oak-a3f2。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "WorktreeCreate",
"name": "feature-auth"
}
WorktreeCreate 輸出
WorktreeCreate hook 不使用標準的允許/封鎖決策模型,而是由 hook 的成功或失敗決定結果。hook 必須回傳所建立的 worktree 目錄路徑:
- 命令 hook(
type: "command"):將路徑印為 stdout 的最後一個非空行。Claude Code 在讀取該行之前會移除 ANSI 跳脫碼,因此在您的echo之前印出的 shell 啟動橫幅會被忽略。請將任何其他 hook 輸出重新導向至 stderr。 - HTTP hook(
type: "http"):在回應主體中回傳{ "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } }。
如果 hook 失敗或未產生路徑,worktree 建立將失敗並顯示錯誤。
Claude Code 會以 hook 執行時所在的目錄解析相對路徑,並摺疊其中的任何 . 或 .. 區段。如果產生的路徑不是 Claude Code 可以進入的目錄,工作階段會印出指明該路徑的錯誤,並以代碼 1 結束。
Claude Code 會拒絕包含 . 或 .. 區段的絕對路徑,以及任何經過儲存庫根目錄下方符號連結的路徑,因為提交至儲存庫的符號連結可能會將 worktree 重新導向至儲存庫之外。錯誤會指明被拒絕的元件。請回傳不經過儲存庫內符號連結的正規化路徑。在 v2.1.216 之前,worktree 建立會直接採用 hook 的路徑,不進行此項檢查。
WorktreeRemove
在移除 worktree 時執行。這是 WorktreeCreate 對應的清理事件。此事件會在以下情況觸發:
- 您結束
--worktree工作階段並選擇移除它 - 具有
isolation: "worktree"的 subagent 完成 - 您刪除一個其 worktree 由 hook 建立的背景工作階段
對於基於 git 的 worktree,Claude Code 會透過 git worktree remove 自動處理清理。如果您設定了 WorktreeCreate hook,請搭配 WorktreeRemove hook 來控制其所建立 worktree 的清理:
- 沒有 WorktreeRemove hook:當您結束
--worktree工作階段並選擇移除時,Claude Code 會改用git worktree remove --force處理您的 WorktreeCreate hook 回傳的路徑,因此 git 能識別的 worktree 會被移除。git 無法識別的 worktree,例如您的 hook 以非 git 版本控制系統建立的 worktree,會保留在磁碟上。關於刪除背景工作階段時如何處理 hook 建立的 worktree,請參閱 agent view 的刪除規則。 - Hook 以 0 結束:該 worktree 視為已移除。Claude Code 不會從 hook 讀取其他任何內容,因此請確保您的 hook 已刪除該目錄。
- Hook 以非零結束:如果
worktree_path處的目錄在之後仍然存在,移除即失敗,且 worktree 會保留在磁碟上,不會改用 git。在以非零結束之前已刪除目錄的 hook 視為已移除。關於失敗的回報方式,請參閱 WorktreeRemove 輸入。
Claude Code 絕不會刪除屬於 hook 建立之 worktree 的分支,因為它只知道您的 WorktreeCreate hook 回傳的路徑。如果您的 WorktreeCreate hook 建立了分支,請在 WorktreeRemove hook 中將其刪除。
Claude Code 會捨棄 WorktreeRemove hook 的 JSON 輸出欄位,例如 systemMessage 和 continue。
對於背景工作階段的刪除,Claude Code 會在執行 hook 之前驗證所儲存的 worktree 路徑,並拒絕本身為符號連結或經過儲存庫根目錄下方符號連結的路徑。只有當您在 agent view 中確認刪除時,hook 才會針對仍含有檔案的 worktree 執行;對於這類 worktree,claude rm 則會保留工作階段和 worktree。在 v2.1.216 之前,hook 會在未經這些檢查的情況下針對儲存的路徑執行。
Claude Code 會將 WorktreeCreate 回傳的路徑作為 hook 輸入中的 worktree_path 傳入。以下範例讀取該路徑並移除目錄:
{
"hooks": {
"WorktreeRemove": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
}
]
}
]
}
}
WorktreeRemove 輸入
除了通用輸入欄位之外,WorktreeRemove hook 還會收到 worktree_path 欄位,即要移除之 worktree 的絕對路徑。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "WorktreeRemove",
"worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
}
WorktreeRemove hook 的退出碼決定結果。當 hook 以非零結束,且 worktree_path 處的目錄在之後仍然存在時,移除即失敗:
- worktree 會保留在磁碟上,hook 的命令和 stderr 會寫入偵錯日誌。
- 如果您正在刪除背景工作階段,該工作階段也會保留。agent view 中的拒絕訊息會回報 hook 的結束方式(例如
exited 1)、引用其 stderr 的開頭,並說明再次刪除該工作階段是否仍會移除該目錄。
PreCompact
在 Claude Code 即將執行壓縮操作之前執行。
matcher 值表示壓縮是手動觸發還是自動觸發:
| Matcher | 觸發時機 |
|---|---|
manual |
/compact |
auto |
當對話達到自動壓縮視窗時自動壓縮 |
以代碼 2 結束可封鎖壓縮。對於手動 /compact,stderr 訊息會顯示給使用者。您也可以回傳含有 "decision": "block" 的 JSON 來封鎖。
封鎖自動壓縮的效果取決於其觸發時機。如果壓縮是在達到上下文限制之前主動觸發的,Claude Code 會略過壓縮,對話會在未壓縮的狀態下繼續。如果壓縮是為了從 API 已回傳的上下文限制錯誤中恢復而觸發的,底層錯誤就會浮現,目前的請求會失敗。
Claude Code 會捨棄 PreCompact hook 的 systemMessage 和 continue 欄位。
PreCompact 輸入
除了通用輸入欄位之外,PreCompact hook 還會收到 trigger 和 custom_instructions。對於 manual,custom_instructions 包含使用者傳入 /compact 的內容,未傳入任何內容時為 null。對於 auto,custom_instructions 為 null。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "PreCompact",
"trigger": "manual",
"custom_instructions": null
}
PostCompact
在 Claude Code 完成壓縮操作之後執行。使用此事件來回應新的壓縮狀態,例如記錄產生的摘要或更新外部狀態。Claude Code 會捨棄 PostCompact hook 的 systemMessage 和 continue 欄位。
適用與 PreCompact 相同的 matcher 值:
| Matcher | 觸發時機 |
|---|---|
manual |
/compact 之後 |
auto |
當對話達到自動壓縮視窗而自動壓縮之後 |
PostCompact 輸入
除了通用輸入欄位之外,PostCompact hook 還會收到 trigger 和 compact_summary。compact_summary 欄位包含壓縮操作所產生的對話摘要。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "PostCompact",
"trigger": "manual",
"compact_summary": "Summary of the compacted conversation..."
}
PostCompact hook 沒有決策控制。它們無法影響壓縮結果,但可以執行後續工作。
PreModelSwitch
在 Claude Code 套用您或用戶端所請求的模型切換之前執行。使用它來封鎖切換、要求確認,或在切換發生之前顯示切換的成本。
PreModelSwitch 需要 Claude Code v2.1.251 或更新版本。Claude Code 會針對以下請求執行它:
/model <name>和/model選擇器Option+P或Alt+P模型選擇器/config中的 Model 設定- 開啟快速模式而導致工作階段的模型變更時
- 來自 Agent SDK 主機或 Remote Control 的
set_model請求,或apply_flag_settings請求中的模型變更
對於 Claude Code 自行進行的切換,例如自動模型備援或在您恢復工作階段時還原模型,Claude Code 不會執行 PreModelSwitch hook。這些變更只會觸發 PostModelSwitch。
Claude Code 會將 matcher 與工作階段要切換到的模型的正式名稱進行比對,並忽略任何 [1m] 後綴。別名(例如 opus)、帶日期的模型 ID,以及供應商專屬 ID(例如 Amazon Bedrock 模型 ID)都會比對到它們解析出的同一個正式名稱,因此 claude-opus-5 涵蓋 Opus 5 的所有寫法。
當 Claude Code 無法判斷目標的正式名稱時,例如只有您的 LLM 閘道知道的自訂模型 ID,它會不論 matcher 為何都執行每個 PreModelSwitch hook。因此,會進行封鎖的 hook 應檢查其輸入中的 to_model,而不是僅依賴 matcher。
matcher 可以寫成確切名稱、以 | 分隔的清單(例如 claude-opus-4-6|claude-opus-5),或正規表示式(例如 .*opus.*)。以下範例使用確切名稱 matcher,並同時檢查 hook 輸入中的 to_model,因此它會以代碼 2 結束來拒絕切換到 Opus 4.6,並允許任何其他目標通過:
該命令使用 jq 檢查 to_model:
{
"hooks": {
"PreModelSwitch": [
{
"matcher": "claude-opus-4-6",
"hooks": [
{
"type": "command",
"command": "jq -e '.to_model | test(\"opus-4-6\")' > /dev/null && { echo 'Opus 4.6 is retired for this project. Use a newer model.' >&2; exit 2; }; exit 0"
}
]
}
]
}
}
註冊一個透過 PowerShell 執行指令碼的命令 hook:
{
"hooks": {
"PreModelSwitch": [
{
"matcher": "claude-opus-4-6",
"hooks": [
{
"type": "command",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-opus-46.ps1"
]
}
]
}
]
}
}
將此指令碼儲存至專案中的 .claude/hooks/block-opus-46.ps1:
$hookInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
if ($hookInput.to_model -match 'opus-4-6') {
[Console]::Error.WriteLine('Opus 4.6 is retired for this project. Use a newer model.')
exit 2
}
exit 0
若要確認 hook 正常運作,請在執行其他模型的工作階段中執行 /model claude-opus-4-6。Claude Code 會保留目前的模型,並回報 PreModelSwitch hook 封鎖了切換,並以您的訊息作為原因。
PreModelSwitch 輸入
除了通用輸入欄位之外,PreModelSwitch hook 還會收到下表中的欄位。最後五個欄位描述將對話重新傳送至新模型的成本,讓 hook 能在切換發生之前顯示該數字。
| 欄位 | 類型 | 說明 |
|---|---|---|
from_model |
string | 切換前的模型 ID |
to_model |
string | 切換後的模型 ID。matcher 會與此模型的正式名稱進行比對 |
requested_model |
string 或 null |
請求中指定的模型:別名(例如 opus)、完整模型 ID,或當請求的是預設模型時為 null |
source |
string | 請求的來源:"command" 表示 /model <name>、/config 中的 Model 設定,或開啟快速模式;"picker" 表示模型選擇器;"sdk" 表示來自 Agent SDK 主機或 Remote Control 的 set_model 請求,或 apply_flag_settings 請求中的模型變更 |
context_tokens |
number | 下一個請求作為提示詞重新傳送的 token 數:主對話中最後一個回應的輸入、快取讀取、快取建立和輸出 token 的總和。在第一個回應之前為 0 |
prompt_cache_warm |
boolean | 目前模型的提示快取是否可能仍處於暖狀態,亦即切換會使其失效 |
cache_ttl |
string | Claude Code 為此工作階段請求的提示快取存留期:"5m" 或 "1h" |
estimated_cache_write_usd |
number | 以 cache_ttl 費率將 context_tokens 寫入 to_model 提示快取的預估成本(美元),不含下一個回應。伺服器可能不需要重新快取整個上下文,因此請將其視為估計值 |
pricing |
string | Claude Code 計算 estimated_cache_write_usd 的方式:"configured" 表示在您的組織已設定自有費率時採用該費率,"catalog" 表示採用定價表價格,"default" 表示 to_model 沒有已知價格而 Claude Code 採用預設費率 |
以下範例顯示在執行 Sonnet 5 的工作階段中執行 /model opus 時的輸入:
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "PreModelSwitch",
"from_model": "claude-sonnet-5",
"to_model": "claude-opus-5",
"requested_model": "opus",
"source": "command",
"context_tokens": 182340,
"prompt_cache_warm": true,
"cache_ttl": "5m",
"estimated_cache_write_usd": 1.1396,
"pricing": "catalog"
}
PreModelSwitch 決策控制
PreModelSwitch hook 可以取消切換、要求使用者確認,或讓切換繼續進行。退出碼 2 或頂層 decision: "block" 會取消切換。
若需要更精細的控制,請在 hookSpecificOutput 物件中回傳 permissionDecision 和 permissionDecisionReason,與 PreToolUse 相同。PreModelSwitch 接受 "allow"、"deny" 和 "ask"。它不接受 "defer"、updatedInput 或 additionalContext。下表說明這兩個欄位:
| 欄位 | 說明 |
|---|---|
permissionDecision |
"allow" 會繼續進行並略過提示快取處於暖狀態時 Claude Code 顯示的確認。"deny" 會取消切換。"ask" 會提示使用者確認 |
permissionDecisionReason |
對於 "deny",會作為切換被封鎖的原因顯示給使用者,或作為 set_model 請求的錯誤回傳。對於 "ask",會顯示在確認提示中。對於 "allow" 則會被忽略 |
只有互動式工作階段中的 /model 能顯示 "ask" 提示。在其他所有使用介面上,包括使用 -p 旗標的非互動模式、/config 和 set_model 請求,Claude Code 都會將 "ask" 視為拒絕。
以下範例要求使用者確認,並引用 context_tokens 中的 token 數:
{
"hookSpecificOutput": {
"hookEventName": "PreModelSwitch",
"permissionDecision": "ask",
"permissionDecisionReason": "Switching now re-sends about 180k tokens to the new model. Continue?"
}
}
當多個 PreModelSwitch hook 回傳不同的決策時,優先順序為 deny > ask > allow。
無論決策為何,Claude Code 都會向使用者顯示您的 hook 回傳的任何 systemMessage,因此成本回報 hook 可以回傳 {"systemMessage": "..."} 並以 0 結束。
在逾時前未回應的 PreModelSwitch hook 會封鎖切換。相較之下,在 PreToolUse 上,逾時的命令 hook 會讓工具呼叫繼續進行。此事件的預設逾時為 30 秒。PreModelSwitch 只執行 command、http 和 mcp_tool hook,因此 prompt 和 agent 的預設值不適用。
以 0 或 2 以外的代碼結束且未印出 JSON 決策的 hook 不會封鎖:Claude Code 會顯示其 stderr 並套用切換,如其他退出碼中所述。
PostModelSwitch
在工作階段的模型變更之後執行。使用它來為 Claude 提供特定於模型的指引,而無需編輯每個 CLAUDE.md,例如適用於特定模型的全組織指令。
PostModelSwitch 需要 Claude Code v2.1.251 或更新版本。它無法封鎖,因為模型已經變更。Claude Code 會在以下任何變更之後執行 PostModelSwitch hook:
當備援模型鏈中的模型服務某個回合時,Claude Code 不會執行 PostModelSwitch hook,因為該替換只持續一個回合,且不會變更工作階段的模型。
matcher 遵循與 PreModelSwitch 相同的規則:Claude Code 會將其與工作階段所切換到的模型的正式名稱進行比對。
以下範例會在工作階段的模型變更為任何 Opus 模型時新增指引:
{
"hooks": {
"PostModelSwitch": [
{
"matcher": ".*opus.*",
"hooks": [
{
"type": "command",
"command": "echo 'On Opus, delegate implementation work to subagents and keep this conversation for planning and review.'"
}
]
}
]
}
}
若要確認 hook 正常運作,請從執行其他模型的工作階段切換到 Opus 模型,例如在 Sonnet 工作階段中執行 /model opus,然後詢問 Claude 它對目前模型有哪些指引。
PostModelSwitch 輸入
PostModelSwitch hook 會收到與 PreModelSwitch 相同的欄位,其中 hook_event_name 設定為 "PostModelSwitch",且多了兩個 source 值:"auto" 表示自動備援或 Claude Code 自行進行的其他變更,"resume" 表示在您恢復工作階段時還原的模型。
當 source 為 "auto" 時,requested_model 為 null。當 source 為 "resume" 時,它是 Claude Code 所還原的已儲存模型設定。
PostModelSwitch 決策控制
Claude Code 會在結束代碼為 0 時取用您的 hook 的純文字 stdout,或取用 JSON 輸出中的 additionalContext,並隨切換後的下一個請求傳遞給 Claude。除了所有 hook 都可使用的 JSON 輸出欄位之外,您還可以回傳:
| 欄位 | 說明 |
|---|---|
additionalContext |
隨下一個請求加入 Claude 上下文的字串。請參閱為 Claude 新增上下文 |
如果在您傳送下一個提示詞後五秒內 hook 尚未完成,Claude Code 會在不含該輸出的情況下傳送該請求,並改為將輸出附加到之後的請求。如果模型在下一個請求之前變更多次,Claude Code 只會傳遞最後一次切換之目標模型的輸出。
SessionEnd
在 Claude Code 工作階段結束時執行。適用於清理工作、記錄工作階段 統計資料或儲存工作階段狀態。支援使用 matcher 依結束原因進行篩選。
hook 輸入中的 reason 欄位表示工作階段結束的原因:
| 原因 | 說明 |
|---|---|
clear |
使用 /clear 命令清除工作階段 |
resume |
透過互動式 /resume 切換工作階段 |
logout |
使用者登出 |
prompt_input_exit |
使用者在提示詞輸入可見時結束 |
other |
其他結束原因 |
bypass_permissions_disabled |
已在 v2.1.234 中移除;Claude Code 不會傳送此值。請從您的 SessionEnd matcher 中移除它 |
SessionEnd 輸入
除了通用輸入欄位之外,SessionEnd hook 還會收到表示工作階段結束原因的 reason 欄位。所有值請參閱上方的原因表格。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SessionEnd",
"reason": "other"
}
SessionEnd hook 沒有決策控制。它們無法封鎖工作階段終止,但可以執行清理工作。Claude Code 會捨棄它們的 JSON 輸出欄位,例如 systemMessage。
SessionEnd hook 的預設逾時為 1.5 秒。此逾時適用於您結束、執行 /clear 或透過互動式 /resume 切換工作階段時。您可以透過兩種方式給予 hook 更多時間:
- 個別 hook 的
timeout:在該 hook 的設定中設定timeout。整體預算會自動提高,以符合您設定檔中最高的個別 hooktimeout,上限為 60 秒。如果您以這種方式提高預算,沒有自己timeout的 hook 仍會保留預設值。在外掛程式提供的 hook 上設定的逾時不會提高預算。 CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS:以毫秒為單位設定此環境變數,以明確覆寫預算。您設定的值也會成為每個沒有自己timeout之 hook 的逾時。
以下範例將預算設定為 5 秒:
CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude
在 v2.1.268 之前,CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS 只會提高整體預算,沒有自己 timeout 的 hook 仍會在 1.5 秒後被取消。
Elicitation
在 MCP 伺服器於工作進行中請求使用者輸入時執行。根據預設,Claude Code 會顯示互動式對話方塊供使用者回應。hook 可以攔截此請求並以程式化方式回應,完全略過對話方塊。
matcher 欄位會與 MCP 伺服器名稱進行比對。
Elicitation 輸入
除了通用輸入欄位之外,Elicitation hook 還會收到 mcp_server_name、message,以及選用的 mode、url、elicitation_id 和 requested_schema 欄位。
對於表單模式的 elicitation(最常見的情況):
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Elicitation",
"mcp_server_name": "my-mcp-server",
"message": "Please provide your credentials",
"mode": "form",
"requested_schema": {
"type": "object",
"properties": {
"username": { "type": "string", "title": "Username" }
}
}
}
對於 URL 模式的 elicitation,用於基於瀏覽器的身分驗證:
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Elicitation",
"mcp_server_name": "my-mcp-server",
"message": "Please authenticate",
"mode": "url",
"url": "https://auth.example.com/login"
}
Elicitation 輸出
若要在不顯示對話方塊的情況下以程式化方式回應,請回傳含有 hookSpecificOutput 的 JSON 物件:
{
"hookSpecificOutput": {
"hookEventName": "Elicitation",
"action": "accept",
"content": {
"username": "alice"
}
}
}
| 欄位 | 值 | 說明 |
|---|---|---|
action |
accept、decline、cancel |
是否接受、拒絕或取消請求 |
content |
object | 要提交的表單欄位值。僅在 action 為 accept 時使用 |
退出碼 2 會拒絕 elicitation。Claude Code 不會在任何地方顯示您的 stderr 訊息。
Claude Code 會依據 Elicitation hook JSON 輸出中的 hookSpecificOutput 採取行動,並捨棄 systemMessage 和 continue。
ElicitationResult
在使用者回應 MCP elicitation 之後執行。hook 可以在回應傳回 MCP 伺服器之前觀察、修改或封鎖該回應。
matcher 欄位會與 MCP 伺服器名稱進行比對。
ElicitationResult 輸入
除了通用輸入欄位之外,ElicitationResult hook 還會收到 mcp_server_name、action,以及選用的 mode、elicitation_id 和 content 欄位。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "ElicitationResult",
"mcp_server_name": "my-mcp-server",
"action": "accept",
"content": { "username": "alice" },
"mode": "form",
"elicitation_id": "elicit-123"
}
ElicitationResult 輸出
若要覆寫使用者的回應,請回傳含有 hookSpecificOutput 的 JSON 物件:
{
"hookSpecificOutput": {
"hookEventName": "ElicitationResult",
"action": "decline",
"content": {}
}
}
| 欄位 | 值 | 說明 |
|---|---|---|
action |
accept、decline、cancel |
覆寫使用者的動作 |
content |
object | 覆寫表單欄位值。僅在 action 為 accept 時有意義 |
退出碼 2 會封鎖回應,將實際動作變更為 decline。Claude Code 不會在任何地方顯示您的 stderr 訊息。
Claude Code 會依據 ElicitationResult hook JSON 輸出中的 hookSpecificOutput 採取行動,並捨棄 systemMessage 和 continue。
基於提示的 hooks
除了命令、HTTP 和 MCP tool hooks 外,Claude Code 還支援基於提示的 hooks(type: "prompt"),使用 LLM 評估是否允許或阻止操作,以及代理 hooks(type: "agent"),生成具有工具存取權限的代理驗證器。並非所有事件都支援每種 hook 類型。
支援所有五種 hook 類型(command、http、mcp_tool、prompt 和 agent)的事件:
PermissionDeniedPostToolBatchPostToolUsePostToolUseFailurePreToolUseStopSubagentStopTaskCompletedTaskCreatedTeammateIdleUserPromptExpansionUserPromptSubmit
PermissionRequest 支援 command、http、mcp_tool 和 prompt hooks,但不支援 agent hooks。如果您在此事件上配置代理 hook,Claude Code 會跳過它,權限流程保持不變。要從 hook 允許或拒絕,請從命令或 HTTP hook 返回決定物件。
支援 command、http 和 mcp_tool hooks 但不支援 prompt 或 agent 的事件:
ConfigChangeCwdChangedDirectoryAddedElicitationElicitationResultFileChangedInstructionsLoadedMessageDisplayNotificationPostCompactPostModelSwitchPreCompactPreModelSwitchSessionEndStopFailureSubagentStartWorktreeCreateWorktreeRemove
SessionStart 和 Setup 支援 command 和 mcp_tool hooks,而 MCP tool hook 欄位描述了它們的 mcp_tool hooks 何時執行。它們不支援 http、prompt 或 agent hooks。
基於提示的 hooks 如何工作
基於提示的 hooks 不執行 Bash 命令,而是:
- 將 hook 輸入和您的提示發送到 Claude 模型,預設為 Claude Code 用於背景功能的模型
- LLM 以包含決定的結構化 JSON 回應
- Claude Code 自動處理決定
提示 hook 配置
將 type 設定為 "prompt" 並提供 prompt 字串而不是 command。使用 $ARGUMENTS 佔位符將 hook 的 JSON 輸入資料注入到您的提示文字中。
此 Stop hook 詢問 LLM 在允許 Claude 完成之前是否應該評估所有任務是否完成:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
}
]
}
]
}
}
| 欄位 | 必需 | 描述 |
|---|---|---|
type |
是 | 必須為 "prompt" |
prompt |
是 | 要發送到 LLM 的提示文字。使用 $ARGUMENTS 作為 hook 輸入 JSON 的佔位符。如果 $ARGUMENTS 不存在,輸入 JSON 會附加到提示 |
model |
否 | 用於評估的模型。預設為 Claude Code 用於背景功能的模型 |
timeout |
否 | 逾時(秒)。預設值:30 |
continueOnBlock |
否 | 在適用的事件上,true 將 ok: false 原因反饋給 Claude 並繼續而不是結束轉換。預設值:false。請參閱回應架構以了解每個事件的行為 |
回應架構
LLM 必須以包含以下內容的 JSON 回應:
{
"ok": true | false,
"reason": "Explanation for the decision",
"impossible": true | false
}
| 欄位 | 描述 |
|---|---|
ok |
true 允許操作。false 時,請參閱下面的每個事件行為 |
reason |
當 ok 為 false 時必需 |
impossible |
選用。當模型判斷條件永遠無法滿足時,模型會以 ok: false 返回它。在 Stop 和 SubagentStop 上,Claude Code 會讓轉換結束而不是反饋原因。代理 hooks 和其他事件會忽略它 |
ok: false 時發生的情況取決於事件:
Stop和SubagentStop:原因被反饋給 Claude 作為其下一個指令,轉換繼續,除非回應也設定impossible: true,在這種情況下 Claude Code 允許停止,轉換結束PreToolUse:工具呼叫被拒絕;預設情況下轉換結束,拒絕原因在聊天中顯示為警告行。設定continueOnBlock: true以改為將原因作為工具錯誤返回給 Claude,使其可以調整並繼續,相當於命令 hook 的permissionDecision: "deny"。在 v2.1.210 之前,拒絕原因被作為工具錯誤返回給 Claude,轉換繼續PostToolUse:預設情況下轉換結束,原因在聊天中顯示為警告行。設定continueOnBlock: true以將原因反饋給 Claude 並繼續轉換PostToolBatch、UserPromptSubmit和UserPromptExpansion:轉換結束,原因顯示為警告行。這些事件在decision: "block"上結束轉換,無論continue如何PostToolUseFailure和TaskCreated:原因作為工具錯誤返回給 Claude,轉換繼續,無論continueOnBlock如何TaskCompleted:當它因為任務在轉換期間被標記為完成而觸發時,原因作為工具錯誤返回給 Claude,轉換繼續,無論continueOnBlock如何。當它因為隊友停止而觸發時,它的行為類似TeammateIdle並預設停止隊友TeammateIdle:預設情況下隊友停止,原因顯示為警告行。設定continueOnBlock: true以將原因反饋給隊友並保持其工作狀態PermissionRequest:ok: false沒有效果。要從 hook 拒絕批准,請使用命令 hook返回hookSpecificOutput.decision.behavior: "deny"PermissionDenied:ok: false沒有效果,因為拒絕已經發生。此事件讀取的唯一輸出是hookSpecificOutput.retry,提示和代理 hooks 無法設定。它們在此事件上執行,但其輸出被丟棄。使用命令 hook返回retry
如果您需要對任何事件進行更精細的控制,請使用命令 hook,其中包含決定控制中描述的每個事件欄位。
在停止前檢查多個條件
此 Stop hook 使用詳細提示在允許 Claude 停止之前檢查三個條件。SubagentStop hooks 使用相同的格式來評估 subagent 是否應該停止。如果模型因為條件尚未滿足而返回 "ok": false,Claude 繼續工作,提供的原因作為其下一個指令:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
"timeout": 30
}
]
}
]
}
}
基於代理的 hooks
代理 hooks 是實驗性的。行為和配置可能在未來版本中變更。對於生產工作流程,建議使用命令 hooks。
基於代理的 hooks(type: "agent")類似於基於提示的 hooks,但具有多輪工具存取。代理 hook 不是單一 LLM 呼叫,而是生成一個可以讀取檔案、搜尋程式碼和檢查程式碼庫以驗證條件的 subagent。代理 hooks 支援與基於提示的 hooks 相同的事件,除了 PermissionRequest。
代理 hooks 如何工作
當代理 hook 觸發時:
- Claude Code 生成一個 subagent,使用您的提示和 hook 的 JSON 輸入
- Subagent 可以使用 Read、Grep 和 Glob 等工具進行調查
- 在最多 50 輪後,subagent 返回結構化的
{ "ok": true/false }決定 - Claude Code 允許該動作(如果
ok是true)。如果ok是false,Claude Code 會以與提示 hook 相同的方式處理阻止,該提示 hook 在該事件上具有continueOnBlock: true,如回應架構下所列
代理 hooks 在驗證需要檢查實際檔案或測試輸出時很有用,而不僅僅是評估 hook 輸入資料。
代理 hook 配置
將 type 設定為 "agent" 並提供 prompt 字串,使用 $ARGUMENTS 作為 hook 輸入 JSON 的佔位符。配置欄位與提示 hooks 相同,除了代理 hooks 具有更長的預設逾時 60 秒,且沒有 continueOnBlock 欄位。
回應架構是 { "ok": true } 允許或 { "ok": false, "reason": "..." } 阻止。在 ok: false 時,Claude Code 會以處理提示 hook 且具有 continueOnBlock: true 的相同方式處理代理 hook;代理 hooks 沒有 continueOnBlock 欄位,且不支援提示 hook 的 impossible 欄位。
此 Stop hook 驗證所有單元測試通過,然後允許 Claude 完成:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
"timeout": 120
}
]
}
]
}
}
在背景執行 hooks
預設情況下,hooks 會阻止 Claude 的執行,直到它們完成。對於長時間執行的任務,如部署、測試套件或外部 API 呼叫,設定 "async": true 以在背景執行 hook,同時 Claude 繼續工作。非同步 hooks 無法阻止或控制 Claude 的行為:回應欄位,如 decision、permissionDecision 和 continue 沒有效果,因為它們會控制的操作已經完成。
配置非同步 hook
將 "async": true 新增到命令 hook 的配置以在背景執行它而不阻止 Claude。此欄位僅在 type: "command" hooks 上可用。
此 hook 在每個 Write 工具呼叫後執行測試指令碼。Claude 立即繼續工作,同時 run-tests.sh 執行。當指令碼完成時,其輸出在下一個對話輪次上傳遞:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "/path/to/run-tests.sh",
"async": true
}
]
}
]
}
}
一旦非同步 hook 在背景執行,Claude Code 不會對其強制執行 timeout。Claude Code 仍然會對使用 asyncRewake 執行的 hook 強制執行 timeout。
Claude Code 只在工作階段執行時傳遞非同步 hook 的結果:
- 在非互動模式中使用
-p旗標,Claude Code 會在清理時終止任何仍在執行的非同步 hook,並以cancelled結果完成它 - 如果你的 hook 工作必須超越
claude -p工作階段,請從它啟動一個完全分離的程序
非同步 hooks 如何執行
當非同步 hook 觸發時,Claude Code 啟動 hook 程序並立即繼續,而不等待它完成。Hook 在 stdin 上接收與同步 hook 相同的 JSON 輸入。
背景程序退出後,Claude Code 會在下一個對話輪次將 hook 的 JSON 回應中的 additionalContext 和 systemMessage 欄位傳遞給 Claude。與同步 hook 的 systemMessage 不同,這兩個欄位都不會顯示給你。
Claude Code 驗證該 JSON 回應是否符合與同步 hooks 相同的輸出結構,並捨棄任何值類型錯誤的欄位,例如不是字串的 systemMessage,而不是傳遞它。使用 --debug 執行以查看命名每個捨棄欄位的警告。在 v2.1.202 之前,來自非同步 hook 的格式不正確的 JSON 輸出可能會導致工作階段崩潰,每次恢復工作階段時都會重複發生崩潰。
非同步 hook 完成通知預設被抑制。要查看它們,請使用 Ctrl+O 啟用詳細模式或使用 --verbose 啟動 Claude Code。
檔案變更後執行測試
此 hook 在 Claude 寫入檔案時在背景啟動測試套件,然後在測試完成時將結果報告回 Claude。將此指令碼儲存到專案中的 .claude/hooks/run-tests-async.sh 並使用 chmod +x 使其可執行:
#!/bin/bash
# run-tests-async.sh
# 從 stdin 讀取 hook 輸入
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# 僅針對原始檔案執行測試
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
exit 0
fi
# 執行測試並通過 additionalContext 報告結果給 Claude
RESULT=$(npm test 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
MSG="Tests passed after editing $FILE_PATH"
else
MSG="Tests failed after editing $FILE_PATH: $RESULT"
fi
jq -nc --arg msg "$MSG" '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $msg}}'
然後將此配置新增到專案根目錄中的 .claude/settings.json。async: true 旗標讓 Claude 在測試執行時繼續工作:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-async.sh",
"args": [],
"async": true
}
]
}
]
}
}
限制
非同步 hooks 與同步 hooks 相比有額外的限制:
- Hook 輸出在下一個對話輪次上傳遞。如果工作階段閒置,回應會等待直到下一個使用者互動。例外:退出代碼為 2 的
asyncRewakehook 即使在工作階段閒置時也會立即喚醒 Claude。 - 每次執行都會建立一個單獨的背景程序。同一非同步 hook 的多次觸發之間沒有去重。
安全考慮
免責聲明
命令 hooks 以您的完整使用者權限執行 shell 命令。它們可以修改、刪除或存取您的使用者帳戶可以存取的任何檔案。在將任何 hook 命令新增到您的設定之前,請審查並測試它們。
工作區信任
Claude Code 在執行任何來自設定檔的 hook 之前會檢查工作區信任。什麼算作受信任取決於工作階段類型:
- 互動式工作階段:Claude Code 會保留來自每個設定檔的 hooks,包括您自己的
~/.claude/settings.json,直到您接受該資料夾的工作區信任對話框,或接受其信任延伸到該資料夾的父目錄 -p或 SDK 工作階段:Claude Code 不會顯示對話框,並將該資料夾視為受信任,因此儲存庫.claude/settings.json中提交的 hooks 會在您從未信任過的資料夾中執行
在您對儲存庫執行 claude -p 之前,如果您沒有編寫該儲存庫,請審查其 .claude/ 設定檔,使用 --bare 開始,或為該執行關閉 hooks,使用 --settings '{"disableAllHooks": true}'。專案子代理中的 Frontmatter hooks 遵循比設定檔 hooks 更嚴格的規則。在您信任資料夾之前執行的內容按工作階段類型列出每種儲存庫內容。
安全最佳實踐
編寫 hooks 時,請記住這些實踐:
- 驗證和清理輸入:永遠不要盲目信任輸入資料
- 始終引用 shell 變數:使用
"$VAR"而不是$VAR - 阻止路徑遍歷:檢查檔案路徑中的
.. - 使用絕對路徑:為指令碼指定完整路徑。在 exec 形式中,使用
${CLAUDE_PROJECT_DIR}且路徑不需要引用。在 shell 形式中,將其包裝在雙引號中 - 跳過敏感檔案:避免
.env、.git/、金鑰等
Windows PowerShell 工具
在 Windows 上,您可以通過在命令 hook 上設定 "shell": "powershell" 在 PowerShell 中執行個別 hooks。Claude Code 自動偵測 pwsh.exe(PowerShell 7 及更新版本的可執行檔),並回退到 powershell.exe(Windows PowerShell 5.1)。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"shell": "powershell",
"command": "Write-Host 'File written'"
}
]
}
]
}
}
若要從 PowerShell shell 形式命令參考專案根目錄,請寫入 ${CLAUDE_PROJECT_DIR} 或 $env:CLAUDE_PROJECT_DIR。Claude Code 會在 PowerShell shell 形式命令中將 ${CLAUDE_PROJECT_DIR}、${CLAUDE_PLUGIN_ROOT} 和 ${CLAUDE_PLUGIN_DATA} 佔位符重寫為 PowerShell 的 ${env:NAME} 形式,無論 hook 是在 settings.json、外掛或 skill 中定義。PowerShell 會在解析後從匯出的環境中解析該值,因此佔位符在雙引號字串內有效,但在單引號字串內無效,因為 PowerShell 永遠不會在單引號字串中展開變數。
不要在 PowerShell hook 中寫入裸露的 $CLAUDE_PROJECT_DIR 拼寫。PowerShell 會將其解析為未定義的本機變數,並將其解析為 $null,這會導致指令碼路徑沒有其專案根目錄前綴。Claude Code 不會重寫該形式;它會在 debug log 中記錄警告。
下面的範例顯示了一個 settings.json hook,它使用 $env: 形式執行專案指令碼:
{
"type": "command",
"shell": "powershell",
"command": "& \"$env:CLAUDE_PROJECT_DIR\\.claude\\hooks\\check.ps1\""
}
偵錯 hooks
Hook 執行詳細資訊被寫入偵錯日誌檔案。使用 claude --debug-file <path> 啟動 Claude Code 以將日誌寫入已知位置,或執行 claude --debug 並在 ~/.claude/debug/<session-id>.txt 讀取日誌。--debug 標誌不列印到終端。
例如,在 Write 上的 PostToolUse hook,其命令列印 hook-ran 會產生如下項目:
2026-07-19T02:03:24.382Z [DEBUG] Hook output does not start with {, treating as plain text
2026-07-19T02:03:24.382Z [DEBUG] "Hook PostToolUse:Write (PostToolUse) success:\nhook-ran"
有關更細粒度的 hook 匹配詳細資訊,設定 CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose 以查看額外的日誌行,例如 hook 匹配器計數和查詢匹配。
有關故障排除常見問題,如 hooks 不觸發、Stop hooks 持續阻擋或配置錯誤,請參閱指南中的 限制和故障排除。有關涵蓋 /context、/doctor 和設定優先順序的更廣泛診斷逐步解說,請參閱 偵錯您的設定。