← 所有文章

Claude Code Hooks:我的95個Hook各自存在的原因

我為Claude Code打造了95個hook。每一個都是因為某件事先出了差錯才誕生的。git-safety-guardian的存在,是因為Claude曾經對main執行了force-push。recursion-guard的存在,是因為一個子代理產生了無限的子程序。blog-quality-gate的存在,是因為我發布了一篇包含7個被動語態句子和一個懸空腳註的文章。1

TL;DR

Claude Code hooks在AI輔助開發的特定生命週期節點執行shell指令。Hooks在一個機率性系統(LLM)之上提供確定性保證(阻擋危險的git指令、注入上下文、強制品質標準)。在我的基礎架構中建構了95個hook之後,我發現最好的hook來自事故,而非規劃。本文涵蓋了架構設計、最關鍵hook背後的起源故事,以及我從9個月的hook開發中學到的模式。


架構設計

Claude Code公開了生命週期事件,讓hook可以在這些節點攔截、修改或阻擋行為:2

工作階段事件

事件 觸發時機 我的Hooks
SessionStart 新工作階段開始時 session-start.sh — 注入日期、驗證venv、初始化遞迴狀態
SessionEnd 工作階段結束時 清理暫存檔案

工具執行事件

事件 觸發時機 我的Hooks
PreToolUse 任何工具執行之前 git-safety-guardian.shrecursion-guard.shcredentials-check.sh
PostToolUse 工具完成之後 post-deliberation.shlog-bash.sh

回應事件

事件 觸發時機 我的Hooks
UserPromptSubmit 使用者送出提示時 上下文注入器(日期、分支、模型資訊)
Stop Claude完成回應時 deliberation-pride-check.shreviewer-stop-gate.sh

每個hook透過stdin接收JSON,並透過stdout進行通訊:

{"decision": "block", "reason": "Force push to main is prohibited"}

或是以退出碼0靜默放行。3


起源故事:最重要的Hooks

Hook 1:git-safety-guardian.sh(PreToolUse:Bash)

事故經過: 在一次早期的Claude Code工作階段中,我要求代理「清理git歷史紀錄」。代理執行了git push --force origin main。這次force push覆蓋了共享分支上三天的提交。我從本機備份中恢復了資料,但4小時的復原過程讓我確信:機率性判斷永遠不應該控制破壞性的git操作。

Hook內容:

#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# Only check git commands
echo "$COMMAND" | grep -qE '\bgit\b' || exit 0

# Block force push to main/master
if echo "$COMMAND" | grep -qiE 'git\s+push\s+.*--force.*\b(main|master)\b'; then
    cat << EOF
{"decision": "block", "reason": "Force push to main/master blocked by safety hook"}
EOF
fi

教訓: 這個hook不會嘗試理解意圖。它對指令字串進行模式匹配。簡單、確定性、無法透過巧妙的提示繞過。代理仍然可以對功能分支執行force-push(有時這是合理的),但main/master永遠受到保護。4

累計攔截次數: 9個月內攔截了8次force-push嘗試。

Hook 2:recursion-guard.sh(PreToolUse:Task)

事故經過: 在建構審議系統時,我執行了一個產生3個探索子代理的工作階段。每個子代理因為缺乏產生限制,又各自產生了自己的子代理。這個遞迴以正常速率10倍的速度消耗API代幣。在注意到加速的代幣消耗後,我手動終止了該工作階段。

Hook內容:

#!/bin/bash
CONFIG_FILE="${HOME}/.claude/configs/recursion-limits.json"
STATE_FILE="${HOME}/.claude/state/recursion-depth.json"

MAX_DEPTH=2
MAX_CHILDREN=5
DELIB_SPAWN_BUDGET=2
DELIB_MAX_AGENTS=12

# Validate integers safely (((VAR++)) crashes with set -e when VAR=0)
is_positive_int() {
    [[ "$1" =~ ^[0-9]+$ ]] && [[ "$1" -gt 0 ]]
}

關鍵設計決策: 代理從父代理繼承產生預算,而非遞增深度。一個預算為12的根代理可以將該預算分配到任何樹狀結構中。基於深度的限制過於僵化(它們會阻止深但窄的鏈式結構,而這些結構其實完全安全)。5

累計阻擋次數: 23次失控的產生嘗試。

Hook 3:blog-quality-gate.sh(Stop)

事故經過: 我發布了一篇部落格文章,其中包含7個被動語態句子、一個在正文中被引用但在參考文獻區缺失的腳註,以及以「was implemented by the team」作為開頭句。這篇文章在編輯器中看起來很精美,但未能通過任何人類審閱者都會發現的基本品質檢查。

Hook功能: 對任何修改過的部落格內容檔案執行我的12模組部落格檢查工具。檢查被動語態、懸空腳註、缺失的meta描述、未標記的程式碼區塊以及引用完整性。每項發現都很具體:「第47行:在『was implemented by the team』中偵測到被動語態。建議:『the team implemented。』」

人類回饋的相似之處: 這個hook批評的是作品,而非操作者。它說「第47行有被動語態」,而非「你寫得很差」。讓人類回饋具建設性的同一原則,也讓自動化回饋變得實用。


95個Hooks背後的模式

組態驅動架構

我的hooks從寫死的值演進為組態驅動的行為:

~/.claude/configs/
├── recursion-limits.json     # Depth, spawn budgets, timeouts
├── deliberation-config.json  # Consensus thresholds per task type
├── consensus-profiles.json   # security=85%, docs=50%
├── circuit-breaker.json      # Failure mode configurations
└── file-scope-rules.json     # Path-scoped hook application

將閾值移至JSON組態檔意味著我可以在不編輯bash腳本的情況下調整行為。當我需要安全相關共識達到85%但文件只需50%時,組態變更只需30秒。程式碼變更則需要編輯、測試和重新部署。6

生命週期分層模式

我的95個hooks形成了四層安全網:

第1層:預防(PreToolUse) — 在壞事發生之前阻止它們。git-safety-guardian、credentials-check、recursion-guard。

第2層:上下文(UserPromptSubmit、SessionStart) — 注入代理所需的資訊。日期、分支、專案上下文、記憶條目、哲學原則。

第3層:驗證(PostToolUse) — 驗證已完成的操作是否符合標準。post-deliberation共識檢查、輸出日誌記錄。

第4層:品質(Stop) — 把關最終輸出。Pride check、品質把關、審閱者停止閘門。

每一層都是獨立的。如果PreToolUse hook靜默失敗,Stop hook仍然會捕捉到品質問題。縱深防禦,應用於AI代理行為。


組態設定

Hooks存放在.claude/settings.json中:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "command": "~/.claude/hooks/git-safety-guardian.sh",
        "timeout": 5000
      }
    ],
    "PreToolUse:Task": [
      {
        "command": "~/.claude/hooks/recursion-guard.sh"
      }
    ]
  }
}

matcher欄位篩選哪些工具觸發該hook。PreToolUse:Task是僅在Task工具調用時觸發的matcher簡寫。非同步hooks(async: true)在背景執行,不會阻擋流程。7

範圍層級

  1. 使用者層級~/.claude/settings.json)— 適用於所有專案。我的95個hooks存放於此。
  2. 專案層級.claude/settings.json)— 新增專案特定的hooks。
  3. 技能/子代理frontmatter — 範圍限定於特定元件的生命週期。8

如果重來,我會怎麼做

從3個hooks開始,而非25個。 我的第一個月產出了25個hooks,其中許多新增了代理本身已具備的上下文。在每次工具呼叫時載入25個hooks的開銷是可以被測量到的。我最終精簡為那些產生真實價值的hooks(預防了真實事故、捕捉到真實的品質問題)。

從第一天就採用組態驅動。 我花了兩週將寫死的閾值重構為JSON組態檔。如果一開始就採用組態驅動,這次重構本來是零成本的。

及早建立測試基礎設施。 前20個hooks沒有測試。當我加入測試工具(48個bash整合測試)時,我發現有3個hooks在邊界案例中靜默失敗。測試應該從第1個hook就一起交付。


關鍵要點

給剛開始使用hooks的開發者: - 從三個hooks開始:git安全(PreToolUse:Bash)、上下文注入(UserPromptSubmit)和品質把關(Stop);只有在發生足以證明其必要性的事故時才增加更多 - 使用決策時機框架:hook架構是不可逆的(95個hooks依賴於它),因此在編寫hooks之前,先投資於生命週期模型

給正在標準化hooks的團隊: - 在使用者層級標準化hooks,讓每位團隊成員獲得相同的安全防護 - 追蹤hook指標(被阻擋的操作、被攔截的事故)以證明維護成本的合理性 - 每月審查hook日誌,識別值得自動化的新模式


參考文獻


  1. 作者的hook基礎架構。橫跨6個生命週期事件的95個hooks,歷時9個月開發(2025-2026)。 

  2. Anthropic,“Claude Code Documentation,” 2025。Hook生命週期事件。 

  3. Anthropic,“Claude Code Documentation,” 2025。Hook輸入/輸出JSON格式。 

  4. 作者的git-safety-guardian.sh。8次被攔截的force-push嘗試記錄於~/.claude/state/。 

  5. 作者的recursion-guard.sh。預算繼承模型記載於~/.claude/configs/recursion-limits.json。 

  6. 作者的組態驅動架構。14個JSON組態檔編碼所有hook閾值與規則。 

  7. Anthropic,“Claude Code Documentation,” 2025。Hook組態設定與非同步執行。 

  8. Anthropic,“Claude Code Documentation,” 2025。Hook範圍層級。