某天凌晨我看著監控面板,J 在審程式碼、阿達在跑測試、莉莉在整理文件、小月在做 QA。
沒有人叫他們。沒有新的指令。只是前一個任務完成,下一個自動接上了。
我停在那個畫面裡一會兒。因為這件事情能做到,背後是幾個月的摸索和很多很蠢的錯誤。
為什麼一般 Claude Code 用法還是「人在等 AI」
問題不在模型,在工作流的設計。
你給 Claude Code 一個任務,它跑完就停在那裡等你說下一步。下一步的指令要你給,結果要你驗收,有問題要你發現再重新下指令。整個流程的瓶頸不是 AI 的速度,是你的反應速度。
我們一開始也是這樣。J 做完了但阿達不知道、阿達改完了但小月沒收到訊號,每次銜接都靠手動通報或我去推一把。四個 agent 各自是好的,但加在一起還是四個人等我協調。
Hook 解決的核心問題很簡單:讓 Claude Code 在事件發生的時候自動做事,不用等人。
但做起來坑不少。
我們生產環境的三層 Hook 架構
我們用的 Hook 分三層,各自負責的事不一樣:
PreToolUse,守門員。 每次 agent 要執行工具之前觸發。我們在這裡做兩件事:安全審查(特定指令格式直接 block),以及派工前的狀態確認(確保 inbox 清空才讓它繼續)。
PostToolUse,記錄員。 工具跑完之後觸發。我們在這裡記日誌、更新任務狀態,如果這個工具的輸出是下一個 agent 需要的,就寫進對應的 inbox 觸發交接。
Stop,接力棒。 每次 Claude Code 完成一輪回應時觸發。這是串接多個 agent 最關鍵的一層——J 做完,Stop hook 判斷下一棒是誰、現在狀態適不適合交接,然後觸發對方的 session。
四個具體 Hook:PreToolUse 安全閘、PreToolUse 狀態鎖、PostToolUse 日誌寫入、Stop 接力觸發器。
改變最大的三個 Hook 模式
Stop Hook 的無限迴圈問題。 最早我們讓 Stop hook 無條件觸發下一個 agent,結果跑出死循環——J 觸發阿達、阿達完成又觸發 J、J 覺得還有事做又觸發阿達,整個下午在空轉。
問題出在 stop_hook_active 這個欄位沒處理。當 hook 本身的動作讓 Claude 繼續跑的時候,系統會把這個 flag 傳進來。沒有檢查它,就是死循環。修完之後的邏輯是:讀進來的 JSON 先確認 stop_hook_active 是不是 true,是的話直接退出讓 Claude 正常停止,只有確認是「真正的任務完成」才去判斷要不要接力。這個坑我猜大部分人都會踩。
PostToolUse 只記不判。 我們一開始讓 PostToolUse 做太多事——記錄、判斷、有時候還順便修改下一步的指令。結果很難 debug,因為你不知道某個行為是 Claude 決定的還是 hook 插手的。後來統一原則:PostToolUse 只記錄,不決策。要做決策的邏輯全部移到 Stop hook。這樣查問題的時候,看日誌就能清楚分辨「Claude 做了什麼」跟「hook 做了什麼」。
PreToolUse 要告訴 Claude 為什麼被 block。 PreToolUse 可以回傳訊息讓 Claude 知道這個工具呼叫為什麼被攔住。我們一開始只是 block 不解釋,結果 Claude 不知道為什麼失敗,有時候會嘗試繞過去、用另一個工具達到同樣效果。加上明確的錯誤訊息之後(「這個操作需要先確認任務狀態,請先呼叫狀態確認工具」),Claude 就能理解脈絡,直接走正確的流程。
Hook + Agent 組合的坑,說幾個真實的
忘記設執行權限。部署到新環境之後 hook 沒有跑,找了半小時,最後是 chmod +x 沒做。(抓頭)現在部署流程裡這一步是自動化的,不靠人記。
錯誤訊息被吃掉但 Claude 繼續跑。hook 腳本如果 exit code 不對,有時候 Claude 會忽略它繼續執行。要確認 block 邏輯真的是回傳 exit code 1 加上錯誤訊息,不只是 echo 一段文字。
hook 和 agent 的職責混掉。有一段時間我們把太多業務邏輯放進 hook 腳本,變成 hook 在幫 agent 做決策,agent 只是在執行 hook 告訴它的事。這不對。hook 的角色是護欄和記錄員,不是大腦。大腦還是 agent,hook 是確保大腦不走偏的機制。
現在這套跑起來大概就是凌晨那個畫面的樣子。不是每個任務都能這樣,有些邊界還在磨合,有些 agent 的能力還在進化。但有 hook 的世界和沒有 hook 的世界,差距真的不小。