我已經會寫 PreToolUse/PostToolUse 的設定檔 hook,有必要特地學 Mods 嗎?
不一定。兩者解決的是不同層級的問題:如果你的需求是「在某個工具呼叫前後做檢查、擋下或記錄特定行為」,設定檔 hook 系統已經完全夠用,架構更簡單、不需要寫 JavaScript/TypeScript。
Mods 真正該學的時機,是你發現自己需要的東西設定檔 hook 根本碰不到——例如要在畫面上持續顯示一個狀態(像 burn-meter 那樣的即時花費條),或是需要一個不走完整對話輪就能立即執行的 slash command。這類需求屬於介面渲染跟即時事件改寫的範疇,設定檔 hook 在外部行程運作,架構上就看不到這些內容,只有在 Claude Code 行程內部運作的 Mods 才碰得到。
Mod 裡的 observe、rewrite、answer 三種行為,實務上怎麼選?
選擇邏輯取決於你想不想讓事件繼續往下傳、以及要不要改動它的內容。如果只是想「知道」某個事件發生了(例如記錄一次工具呼叫發生的時間),用 observe——呼叫 next、原樣傳遞,不影響任何後續行為,風險最低。
如果你想改變事件本身的內容、但還是要讓它繼續往下走正常流程(例如在某個提示送出前,自動附加一段額外的上下文),用 rewrite——帶著修改過的副本呼叫 next。如果你想直接攔截、不讓事件繼續往下傳(例如偵測到某個危險操作,直接回傳拒絕結果),才用 answer——不呼叫 next,讓鏈在這裡中斷。三者的風險跟影響範圍依序遞增,寫第一個 Mod 建議先從 observe 開始練手,確認事件訂閱邏輯沒問題,再往 rewrite、answer 推進。
官方說 hook 丟出例外會「安靜地失敗」,這在實務上代表什麼風險?
代表你的 Mod 可能已經壞了一段時間,但畫面上完全沒有明顯警示——因為失敗只會在 transcript 裡留下一行不起眼的提示,不會跳出錯誤彈窗或中斷你正在做的事。如果你的 Mod 負責的是安全性或監控用途(例如偵測危險指令),這種「安靜失敗」特別危險,因為你可能以為防護還在運作,實際上它已經停止回應了。
實務上的應對方式是把測試當成必要步驟,不是可選項:每次修改 Mod 的 hook 邏輯後,主動觸發一次預期會啟動這個 hook 的情境,親自確認它有沒有正常反應,而不是假設沒看到錯誤訊息就代表一切正常。
我寫的 Mod 在重新載入之後,畫面上的狀態就消失了,是哪裡寫錯了?
最可能的原因就是文件特別點出的那個坑:把需要保留的值存在 module 自己的變數裡,而不是存進 session 的狀態儲存區。Claude Code 支援熱重載(hot reload),讓你編輯 Mod 程式碼後不需要重啟整個 session 就能套用變更,但熱重載會重置 module 自己的變數——如果你的狀態是存在一般的 JavaScript 變數裡,重載之後這些資料就跟著消失了。
修正方式是改用 engine 提供的狀態儲存機制,讓需要持續存在的資料(像 burn-meter 的累計花費、code-pet 的目前狀態)透過這套機制讀寫,而不是宣告在 module 頂層的一般變數裡。這樣即使你反覆編輯程式碼、觸發多次熱重載,畫面上顯示的資訊也不會跟著重置。
Claude Code v2.1.287(2026 年 10 月 1 日發布)加入了一個新的擴充層級,叫做 Mods。官方把它描述成「plugins may now modify deeper behavior」——聽起來只是 Plugin 系統的一次升級,但實際拆開來看,Mods 跟一般 Plugin、甚至跟既有的設定檔 hook 系統,運作層級完全不同。
按照 技術文件的定義,一個 Mod 本質上就是一個普通的 Claude Code Plugin,多了一個檔案——稱為 hooks module,裡面要輸出一個叫 register 的函式。整套 Mod 由三個核心檔案組成:plugin manifest、一個指向 hooks module 的 hooks.json,以及 module 本體。Mods 不需要任何編譯步驟,Claude Code 會直接載入 .js 或 .ts 檔案。
最關鍵的差異在於運作位置。既有的設定檔 hook 系統(settings.json 裡設定的 PreToolUse、PostToolUse 等)是在 Claude Code 這個行程外部執行 shell 指令、HTTP 請求或提示訊息,回應的是生命週期事件;Mods 則是直接在 Claude Code 行程內部運作,可以直接碰到提示內容、工具呼叫,甚至介面渲染——這些是設定檔 hook 系統從架構上就碰不到的範圍。
Mod 透過具名事件訂閱處理函式,每個處理函式遵循固定的三參數模式:mods API、事件本身、以及一個 next 函式。處理函式串成一條鏈,可以做三種事:觀察(呼叫 next、原樣把事件往下傳)、改寫(呼叫 next 但帶入修改過的副本,影響後續行為)、攔截回應(直接回傳結果、不呼叫 next,讓這條鏈在這裡中斷)。目前涵蓋七大類事件,包括 tool.call、prompt.submit、turn.step、ui.render 等。
官方文件對能力邊界講得很直接:「只有 Mod 才能畫出一個 pane、加一個不經過 Claude 一輪對話就立即執行的 slash command,或是在事件飛行途中改寫它」——這三件事,外部擴充功能從架構上就做不到。
第一個是 code-pet:在模型選擇器旁邊顯示一隻像素風格的小生物,掛鉤工具呼叫跟測試結果,測試失敗後會變成「生病」狀態,執行破壞性指令時會「驚慌」。這個視覺指示器活在 status line 裡,要求 Mod 追蹤 session 層級的狀態,而不是單純的 module 變數。
第二個是 inbox-alerts:透過 claude.ai 的連接器輪詢 Gmail、Slack 跟日曆,把新進項目顯示成 toast 通知跟一個常駐 pane——toast 是暫時性提示,pane 則持續存在,讓你不用離開終端機就能看到有哪些新工作進來。
第三個是 burn-meter:一個獨立 pane,顯示 session 花費的成長火焰條、方案額度的重置時間條,還把花費金額換算成漢堡跟吉事漢堡的數量來顯示。這個 pane 持續渲染,要求 Mod 從 engine 讀取狀態,而不是在本地儲存。
第一個坑跟錯誤處理有關:一個丟出例外的 hook 會「安靜地失敗」,只在 transcript 裡留下一行不起眼的提示,不會有明顯的錯誤訊息跳出來——這代表撰寫 Mod 時測試格外重要,因為失敗不會主動讓你知道。
第二個坑跟狀態儲存方式有關:常見的錯誤是把需要保留的值存在 module 變數裡,而不是存進 session 的狀態儲存區——因為 hot reload 會重置 module 自己的變數。像 burn-meter 這種需要持續渲染的 pane,必須用 engine 提供的狀態儲存機制,才能確保程式碼被編輯、重新載入後,畫面上的資訊不會憑空消失。
如果你目前的需求只是在工具呼叫前後做檢查、擋下危險指令,設定檔 hook 系統(PreToolUse/PostToolUse)已經完全夠用,不需要跳去寫 Mod。但如果你想做的是畫面層級的東西——一個持續顯示狀態的 pane、一個不需要走完整輪對話就能觸發的 slash command、或是需要即時改寫某個事件內容才能達成的效果——這些需求,只有 Mods 這個層級的擴充能滿足,這也是官方特別把它跟一般 Plugin 系統分開命名的原因。