on(eventName, handler) 註冊 hook。
在開始之前,請先建立您的第一個 mod。對於每個事件及其確切欄位,請參閱參考資料或閱讀您的建置類型。
hook 如何處理事件
hook 位於事件和 Claude Code 對其採取的行動之間,因此它可以觀察事件、重寫事件或自己回答事件。它接收三個引數:mods API 作為$、事件作為 e,以及下一個處理程式作為 next。事件的處理程式形成中介軟體鏈。next(e) 呼叫下一個處理程式,這是另一個 mod 的 hook 或在鏈的末端是 Claude Code 自己的行為,它解析為結果。您的 hook 對 next 的處理決定了它執行以下三項中的哪一項。
觀察事件
若要觀察事件而不改變它,請執行您的工作並傳回next(e)。此 hook 記錄 Claude 即將使用的每個工具:
● my-mod: Claude is about to use Bash,其中 my-mod 是您的外掛程式名稱。工具的執行方式與沒有 mod 時相同。
若要在事件後採取行動,請 await next(e)、執行您的工作,然後傳回結果。此 hook 在每個工具執行後記錄它:
next(e) 解析的內容。
重寫事件
若要變更 Claude Code 作用的內容,例如提示詞的文字,請使用修改後的事件副本呼叫next。事件本身是不可變的:它在每個深度都被凍結,分配給欄位會擲回。此 hook 在傳送前修剪每個提示詞:
await next(e),然後傳回結果的副本,其中欄位已替換。
回答事件
若要自己處理事件,請傳回結果而不呼叫next。這會短路鏈,因此稍後的 mod 和 Claude Code 自己的行為不會執行。此 hook 拒絕每個 Bash 命令:
deny 文字讀取為工具的結果。每個事件都有自己的結果形狀,事件參考資料會列出。
篩選 hook 處理哪些事件
若要僅針對某些事件執行 hook,請將篩選器作為第二個引數傳遞給on。Claude Code 將篩選器稱為 matcher。它是一個物件,其欄位與事件的欄位進行比較,只有當每個欄位都符合時,hook 才會執行。欄位可以是值、允許值的陣列或正規表達式。
此範例中的每一行都為較窄的工具呼叫集合註冊相同的函式 hook:
hook 針對 Bash、Edit 或 Write 呼叫執行一次,並針對名稱以 mcp__github__ 開頭的工具呼叫執行一次。對任何其他工具(例如 Read)的呼叫不符合這三個中的任何一個,因此 hook 不會針對它執行。
事件名稱可以是萬用字元。'classic.*' 符合每個設定 hook 事件。'*' 符合除遙測事件之外的每個事件,遙測事件需使用其自身的名稱以及 { to: 'collector' } 篩選器。
為每個 matcher 註冊一次事件。如果您為 session.start 呼叫 on 兩次而沒有 matcher,模組將無法載入,並出現 on("session.start") is registered twice without a matcher。將您的 mod 在工作階段開始時執行的所有操作放在一個 hook 中。
對 Claude 正在執行的動作設定 hook
處理這些事件,即可在工具呼叫、提示詞或回合發生時檢視或變更它們。如需所有事件以及 hook 可傳回的內容,請參閱事件參考。防護或變更工具呼叫
tool.call hook 會看到 Claude 即將使用的每個工具,因此可以拒絕該呼叫、變更其引數,或讓它通過。tool.call 會在 Claude Code 即將執行工具時觸發,包括 subagent 發出的呼叫以及對 MCP 工具的呼叫。e.tool 是工具的名稱,而工具的引數則是 e 的欄位,例如 Bash 的 e.command。當您呼叫 next(e) 時,Claude Code 會執行權限檢查,然後執行工具。
此 hook 會拒絕執行 force push 的 Bash 命令,並告訴 Claude 原因:
git push --force 時,命令不會執行,也不會出現權限提示,因為 hook 從未呼叫 next。Claude 會將 deny 文字當作工具的結果來讀取,因此請將其撰寫為 Claude 可據以行動的指令。其他所有 Bash 命令的執行方式都與沒有 mod 時相同。
若要在工具執行後採取動作,請 await next(e)、執行您的工作,然後傳回 next 給您的內容。此 hook 會使用 $.ui.log 記錄 Claude 變更的每個 .mdx 檔案,該函式會在逐字稿中加入一行 Claude 不會讀取的淡色文字:
.mdx 檔案後,逐字稿中會出現一行淡色文字,標示該檔案名稱。對於其他類型的檔案,或是遭拒絕或失敗的呼叫,則不會記錄任何內容。Claude 對該呼叫的認知不會改變,因為 hook 傳回的是它所收到的結果。
您的 hook 也可以變更呼叫、重試呼叫、自行回應呼叫,或隱藏其結果:
- 變更呼叫:將變更後的引數傳給
next。 - 重試呼叫:再次呼叫
next(e)。在第一個結果上看到isError的 hook 可以再次執行工具,並傳回該結果。 - 自行回應呼叫:在不呼叫
next的情況下傳回具有result欄位的物件;若為內建工具,請讓result符合該工具本身的結果在您建置版本的型別中的形狀。不會出現權限提示,工具也不會執行,因此您傳回的結果就是 Claude 對所發生事情的全部了解。 - 對 Claude 隱藏結果:在
await next(e)之後傳回{ deny: reason }。Claude 會讀取您的原因,而非next傳回的內容。當工具已執行時,deny 會讓 Claude 看不到其結果,但不會復原工具所做的任何事。當工具已執行且成功時,原因會接在如Bash ran, and a plugin withheld its result:之類的註記之後。
tool.call hook 之前執行,且其中任一個所做的封鎖都是最終決定。
暫停工具呼叫,直到使用者做出決定
hook 可以暫停工具呼叫,並在其繼續之前詢問使用者該怎麼做。tool.call hook 可以在呼叫 next 或傳回之前先 await,而工具呼叫會保持等待狀態直到那時。若要向使用者提出問題,請呼叫 $.ui.ask。它會在 Claude 用來詢問您的對話框中,於您的選項編號清單上方顯示您的問題,並解析為使用者所選的標籤。在您的選項之後,對話框會加入一列供輸入其他答案,以及一列 Chat about this。
此範例中的 RISKY 模式會比對 rm -r、rm -rf、git reset --hard 以及帶有 --force 的 git push,但會漏掉其他寫法,例如 git push -f。此模組會在執行符合該模式的 Bash 命令前先詢問:
rm -rf build 之類的命令時,會出現包含該命令的問題,而命令會等待答案:
- 使用者選擇 Run it:hook 會呼叫
next(e),之後一般的權限檢查仍會執行 - 使用者選擇 Refuse:命令不會執行,Claude 會讀取
deny文字 - 使用者輸入答案:
$.ui.ask會解析為輸入的文字。hook 會將其與Run it比較,因此任何其他文字都會拒絕該命令。 - 沒有人回答:當使用者關閉問題或選擇 Chat about this 時,以及在
claude -p執行中,$.ui.ask會拒絕(reject),因此catch區塊會讓答案維持為Refuse
$.ui.ask 之類的 mods API 呼叫中,因為這段時間不會計入 hook 的時間限制。等待您自己的 promise 所花費的時間則會計入。Claude Code 會略過逾時的 hook,因此被暫停的命令將會執行。
在詢問使用者之前核准或拒絕工具呼叫
若要決定工具呼叫是否可以執行,請處理tool.check,這是 Claude Code 做出該決定的事件。它會在權限規則和設定 hook 做出決定之後觸發,而 next(e) 會解析為它們的決定:allow、ask 或 deny。您的 hook 會傳回該決定或不同的決定。e.input 保存工具的引數,例如 Bash 的 command。
對於固定的命令或路徑,請使用權限規則,例如 Bash(npm test),不需要撰寫程式碼。當決定取決於當下的狀況時,例如目前的 Git 分支或另一個 hook 記錄的值,請處理 tool.check。
此 hook 會在目前分支為 main 時拒絕 git push:
main 上拒絕 git push,並讓其他所有呼叫通過:
- 在
main上:即使有規則允許git push,hook 仍會傳回deny - 在其他分支上,以及對於其他命令:該呼叫會得到與沒有 mod 時相同的決定
main,請在您的 Git 主機上保護該分支。
hook 可以傳回 allow、ask 或 deny,因此它也可以核准受管設定以外的 PreToolUse hook 所封鎖的呼叫。在 plan mode 中,對於非唯讀的呼叫(例如檔案編輯),您的 hook 可以收緊 next(e) 傳回的決定,但無法放寬它。使用 hook 擴充權限列出了哪些決定的優先順序高於 mod。
改寫或補充提示詞
prompt.submit hook 會在回合開始之前看到每個提示詞,因此可以改寫文字或加入內容。e.text 是輸入的內容。
每當提示詞提及 pull request 時,此 hook 就會為 Claude 加入目前的分支名稱:
open a PR for this change 之類的提示詞時,您的訊息在逐字稿中看起來不變,而 Claude 還會在其後讀到如 Current branch: feature/auth 之類的一行。未提及 pull request 的提示詞會原封不動地通過,且不會執行 git。
若要阻止提示詞,請在不呼叫 next 的情況下傳回 { drop: 'the reason' }。文字會回到使用者的提示詞輸入欄中,而使用者會看到 Prompt dropped by a hook: 後接您的原因,因此請以使用者為對象撰寫原因。如果您的 hook 在其 next(e) 呼叫已讓提示詞通過之後才傳回 drop,回合仍會執行,且該 hook 會失敗,並顯示包含 a drop after its next() was answered 的訊息。
其他事件涵蓋 Claude 讀取的其餘內容:prompt.section 用於系統提示詞的每個區段,prompt.context 用於隨第一則訊息送出的上下文,而 skill.prompt 用於 skill 的文字。來自這些 hook、且在請求之間有所變動的文字,會使提示快取失效。在 skill.prompt hook 中,您也可以讀取該 skill 來自哪個外掛或檔案。
追蹤回合
回合是 Claude 為回應一個提示詞所做的一切。處理turn.start、turn.step 和 turn.complete 即可追蹤回合:
請將
turn.step hook 撰寫為非同步產生器(async generator),因為此事件是串流的。yield* next(e) 會在串流時轉送回應,並求值為最終結果。此 hook 會記錄每個請求中有多少是由 Claude API 從提示快取提供:
result.usage 保存 Claude API 為請求回報的 token 計數,以及回應的 model:input_tokens、output_tokens、cache_read_input_tokens 和 cache_creation_input_tokens。此 hook 也會針對 subagent 的請求執行,因此若您只想要主要對話,請檢查 e.agentId。
若要查看 API 在請求期間自行執行的工具呼叫,例如對 advisor 工具的呼叫,請讀取 result.serverToolUses。Claude Code 不會執行這些呼叫,因此不會針對它們觸發任何 tool.call 或 tool.check hook。當回應中沒有此類呼叫時,該欄位不會存在,且此欄位需要 Claude Code v2.1.290 或更新版本。
處理設定 hook 事件
設定 hook 是您在設定檔中設定的 command、HTTP、prompt 和 agent hook。每個設定 hook 事件,例如Stop、SessionEnd 或 PostToolUse,也是一個名為 classic. 後接該設定 hook 事件名稱的事件,例如 classic.Stop。e 是設定 hook 在 stdin 上收到的 JSON,包括 transcript_path。
此 hook 使用在 Claude 完成回應時觸發的 Stop,來記錄工作階段逐字稿的儲存位置:
next(e),因此它只觀察事件,不會改變回合結束的方式。
與其他 mod 並行執行
多個 mod 可以處理相同的事件,其中任何一個都可能失敗。如果您的 mod 阻止工具呼叫,請檢查其在鏈中的位置以及其 hook 失敗時會發生什麼。mod 執行的順序
相同事件上的 hook 形成一個中介軟體鏈。每個 mod 的next 呼叫以下 mod 的 hook,最後一個 next 到達 Claude Code 自己的行為。第一個 mod 是最外層的:它在其他 mod 之前看到事件,在它們之後看到結果,並決定其他 mod 是否執行。稍後的 mod 無法阻止較早的 mod 看到事件。
Claude Code 按每個 mod 的來源順序排列鏈:
- 內建防護
sec-default@builtin,一個內建於 Claude Code 的 mod,/plugin列為cc-plugin-sec-default,其中它載入,您的組織在prependPlugins中列出的 mod,然後是任何其他計為您的組織的 mod,且不在appendPlugins中 - 您安裝的 mod
- 您的組織在
appendPlugins中列出的 mod - 內建於 Claude Code 的其他 mod
dependencies 下列出的 mod 之前執行。在一個模組中,hook 按 register 呼叫 on 的順序執行。
設定 hook 在順序中執行的位置
在設定檔中設定的PreToolUse hook 也在工具呼叫期間執行,在 mod 鏈中的固定點:
- 來自受管設定的
PreToolUsehook:在第一個 mod 的tool.callhook 之前執行,其中任一個所做的封鎖都是最終決定,因此沒有 mod 看到呼叫。 - 來自每個其他設定檔和外掛程式
hooks/hooks.json的PreToolUsehook:在最後一個 mod 呼叫next後執行,作為 Claude Code 自己行為的一部分。回答tool.call而不呼叫next的 mod 會阻止它們執行,呼叫next的 mod 會在它傳回的結果中看到它們的決定。
tool.check hook 可以批准受管設定以外的 PreToolUse hook 所阻止的呼叫,因為 tool.check 會在這些 hook 和權限規則做出決定後才觸發。在 plan mode 中,若此類呼叫為唯讀,tool.check hook 可以批准它,但無法批准檔案編輯或其他非唯讀的呼叫。
處理失敗的 hook
失敗的 hook 不會破壞工作階段,您可以決定接下來會發生什麼。當沒有.catch 處理程式的 hook 擲回、逾時或傳回錯誤形狀的結果時,接下來會發生什麼取決於它是否已呼叫 next:
- 它在呼叫
next之前失敗:Claude Code 跳過它,下一個處理程式代替執行 - 它在
next解析後失敗:該結果成立,沒有任何東西執行第二次
my-mod: tool.call hook skipped: threw Error: boom。您讀取它的位置取決於工作階段,如找出 mod 為什麼不執行任何操作所列。其繪圖不驗證的 ui.render hook 的報告方式不同,如從元素建立樹所述。
若要使阻止呼叫的 hook 失敗關閉,請新增 .catch 錯誤處理程式以代替回答。此處,guard 是您的 hook 函式,處理程式會檢查 next.called,以判斷 guard 失敗時是否已呼叫 next:
guard 在 Bash 呼叫上擲回或逾時時,Claude Code 使用相同的事件呼叫處理程式:
guard在呼叫next之前失敗:命令不會執行,Claude 讀取末尾帶有該種類的deny文字guard在呼叫next之後失敗:處理程式的next(e)會解析為guard的呼叫所產生的結果,而不會再次執行命令,Claude 讀取該結果
guard 尚未呼叫 next,命令接著會如同沒有該 mod 時一樣繼續執行。
相同的處理程式形式也適用於 prompt.submit 或 config.set 上的防護。當 next.called 為 false 時,傳回事件參考針對該事件列出的拒絕:prompt.submit 為 { drop: 'the reason' },config.set 為 { deny: 'the reason' }。
在 tool.check 和 plugin.register,於 next 解析後傳回的拒絕仍然有效,因此直接傳回它,無需檢查 next.called:
tool.check:傳回{ decision: 'deny', reason: 'the reason' }plugin.register:傳回{ refuse: 'the reason' },如在檢查失敗時拒絕 mod 所示
tool.call,於 next 解析後傳回的 deny 會對 Claude 隱藏結果。
後續步驟
- 使用 mods API:新增命令和工具、呼叫模型,以及在計時器上執行工作
- 在介面中繪製:在窗格或提示上方顯示您的 hook 收集的內容
- 測試 mod:從測試中引發任何這些事件
- Mods 參考資料:每個事件、每個 mods API 方法和限制