Claude Code Hooks:我的 95 個 Hook 為何各自存在
我為 Claude Code 建立了 95 個 hook。每一個的存在都源於某次出錯的經驗。git-safety-guardian 的存在,是因為 Claude 曾經強制推送至 main。recursion-guard 的存在,是因為某個子代理生成了無限多的子代理。blog-quality-gate 的存在,是因為我曾發布過一篇含有 7 個被動語態句子與一個懸空註腳的文章。1
Claude Code hook 是在特定生命週期節點(工作階段開始、工具使用前後、提示送出以及回應完成)執行的 shell 指令,能在機率性的 LLM 行為之上,提供確定性的防護欄。 Hook 透過 stdin 接收 JSON,並透過 stdout 回傳決策(阻擋、允許或修改)。它們能執行諸如阻擋強制推送至 main、防止遞迴生成代理、在每個工作階段注入上下文,以及把關 LLM 本身無法保證的輸出品質等政策。
TL;DR
Claude Code hook 會在 AI 輔助開發過程中的特定生命週期節點執行 shell 指令。Hook 在機率性系統(LLM)之上提供確定性保證(阻擋危險的 git 指令、注入上下文、強制品質把關)。在我的基礎設施中建立 95 個 hook 之後,我發現最好的 hook 來自於事故,而不是規劃。本文涵蓋這套架構、我最關鍵幾個 hook 的起源故事,以及從 9 個月 hook 開發中學到的模式。
架構
Claude Code 公開了 26 個生命週期事件,hook 可在其中攔截、修改或阻擋行為(截至 v2.1.116,2026 年 4 月)。2 我的 hook 鎖定其中最常見的六個:
工作階段事件
| 事件 | 觸發時機 | 我的 Hook |
|---|---|---|
| SessionStart | 新工作階段開始 | session-start.sh:注入日期、驗證 venv、初始化遞迴狀態 |
| SessionEnd | 工作階段終止 | 清理暫存檔案 |
工具執行事件
| 事件 | 觸發時機 | 我的 Hook |
|---|---|---|
| PreToolUse | 任何工具執行前 | git-safety-guardian.sh、recursion-guard.sh、credentials-check.sh |
| PostToolUse | 工具完成後 | post-deliberation.sh、log-bash.sh |
回應事件
| 事件 | 觸發時機 | 我的 Hook |
|---|---|---|
| UserPromptSubmit | 使用者送出提示 | 上下文注入器(日期、分支、模型資訊) |
| Stop | Claude 完成回應 | deliberation-pride-check.sh、reviewer-stop-gate.sh |
每個 hook 都透過 stdin 接收 JSON,並透過 stdout 進行溝通:
{"decision": "block", "reason": "Force push to main is prohibited"}
或以退出碼 0 靜默允許。3
起源故事:最重要的幾個 Hook
Hook 1:git-safety-guardian.sh(PreToolUse:Bash)
事故經過: 在早期的一次 Claude Code 工作階段中,我請代理「整理一下 git 歷史」。代理執行了 git push --force origin main。這次強制推送覆蓋掉一個共享分支上三天的提交。我從本地備份中還原,但耗時 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 不試圖理解意圖,而是對指令字串進行模式比對。簡單、確定、無法透過巧妙的提示繞過。代理仍可強制推送至功能分支(有時是合理需求),但 main/master 受到永久保護。4
累計成效: 9 個月內攔截了 8 次強制推送嘗試。
Hook 2:recursion-guard.sh(PreToolUse:Task)
事故經過: 在建置審議系統時,我執行了一個工作階段,它生成了 3 個探索型子代理。每個子代理由於沒有生成上限,又各自生成更多子代理。這種遞迴以正常速度的 10 倍消耗 API token。我在注意到 token 消耗加速後,手動終止了該工作階段。
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 模組部落格 linter。檢查被動語態、懸空註腳、缺失的 meta 描述、未標記的程式碼區塊,以及引文完整性。每個發現都是具體的:「第 47 行:在『was implemented by the team』中偵測到被動語態。建議:『the team implemented』。」
與人類回饋的相似之處: Hook 批評作品,而不是操作者。它說的是「第 47 行有被動語態」,而不是「你寫得很差」。使人類回饋具建設性的同一原則,也使自動化回饋具有實用價值。
95 個 Hook 背後的模式
由配置驅動的架構
我的 hook 從寫死的數值,逐漸演變成由配置驅動的行為:
~/.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 個 hook 構成了一張四層的安全網:
第 1 層:預防(PreToolUse): 在壞事發生前阻止它。git-safety-guardian、credentials-check、recursion-guard。
第 2 層:上下文(UserPromptSubmit、SessionStart): 注入代理所需的資訊。日期、分支、專案上下文、記憶條目、哲學原則。
第 3 層:驗證(PostToolUse): 確認已完成的動作符合標準。審議後的共識檢查、輸出紀錄。
第 4 層:品質(Stop): 把關最終輸出。Pride check、品質把關、reviewer stop gate。這一層實現了後設認知監控,由代理評估自身推理品質,而不僅僅是輸出結果。
各層彼此獨立。即使某個 PreToolUse hook 靜默失敗,Stop hook 仍能抓到品質問題。縱深防禦,應用於 AI 代理行為之上。
設定
Hook 定義於 .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 的簡寫。非同步 hook(async: true)會在背景執行而不阻塞主流程。7
範圍階層
- 使用者層級(
~/.claude/settings.json):套用於所有專案。我的 95 個 hook 都位於此處。 - 專案層級(
.claude/settings.json):新增專案專屬的 hook。 - Skill/Subagent frontmatter: 限定於特定元件的生命週期。8
若能重來一次
從 3 個 hook 開始,而不是 25 個。 我的第一個月就產出了 25 個 hook,其中許多所注入的上下文,代理其實早已擁有。每次工具呼叫都載入 25 個 hook 的負擔是可量測的。最終我把它們精簡到真正產生價值的那些(阻止了真實事故、抓到了真實品質問題)。
從第一天就採用由配置驅動。 我花了兩週把寫死的閾值重構成 JSON 配置。若一開始就採用配置,這段重構完全可以避免。
盡早建立測試基礎設施。 前 20 個 hook 沒有測試。當我加入測試框架(48 個 bash 整合測試)後,發現有 3 個 hook 在邊緣案例下靜默失敗。測試本應隨第一個 hook 一併交付。
重點摘要
給剛開始使用 hook 的開發者: - 從三個 hook 開始:git 安全(PreToolUse:Bash)、上下文注入(UserPromptSubmit)與品質把關(Stop);只有在遇到足以證明其必要性的事故時,才增加更多 - 運用決策時機框架:hook 架構是不可逆的(95 個 hook 依賴於它),因此在撰寫 hook 之前,請先投資於生命週期模型的設計
給正在將 hook 標準化的團隊: - 在使用者層級標準化 hook,讓每位團隊成員都享有相同的安全護欄 - 追蹤 hook 指標(被阻擋的操作、攔截到的事故),以證明維護成本的合理性 - 每月審閱 hook 紀錄,找出值得自動化的新模式
FAQ
什麼是 Claude Code hook?
Hook 是在 Claude Code 工作階段中,於特定生命週期節點執行的 shell 腳本。它們會在特定時刻確定性地觸發:在工具執行前(PreToolUse)、工具完成後(PostToolUse)、送出提示時(UserPromptSubmit)、工作階段開始時(SessionStart),以及 Claude 完成回應時(Stop)。Hook 透過 stdin 接收帶有完整上下文(工具名稱、輸入、工作階段 ID)的 JSON,並可阻擋操作、注入上下文或強制執行品質把關。它們在機率性的 LLM 之上提供確定性保證。
Claude Code 可以執行多少個 hook?會有效能成本嗎?
對 hook 的數量並無硬性限制。我在 26 個可用生命週期事件中的 6 個(截至 v2.1.116,2026 年 4 月)執行了 95 個 hook,每個事件大約增加 200 毫秒的總負擔。實際上的限制在於延遲:每個 hook 都會在工具呼叫前後增加執行時間。關鍵的最佳化方式是使用 dispatcher(每個事件一個,從快取的 stdin 依序執行 hook),而不是各自獨立、分別讀取 stdin 的 hook。在我早期的設定中,獨立 hook 並行寫入會造成 JSON 損毀;使用 dispatcher 徹底消除了這種失敗模式。
Claude Code hook 會在哪些事件上觸發?
截至 v2.1.116(2026 年 4 月),Claude Code 公開了 26 個可供 hook 使用的生命週期事件。我的 hook 所鎖定最常見的六個為:SessionStart(新工作階段開始)、SessionEnd(工作階段終止)、PreToolUse(任何工具執行前)、PostToolUse(工具完成後)、UserPromptSubmit(使用者送出提示),以及 Stop(Claude 完成回應)。其他還包括 SubagentStart、PermissionRequest、PermissionDenied、TaskCreated、CwdChanged、FileChanged、PreCompact 等。PreToolUse 與 PostToolUse 可用如 PreToolUse:Bash 或 PreToolUse:Task 等 matcher 進一步限定範圍,使其僅在特定工具類型上觸發。每個事件都透過 stdin 接收帶有相關上下文的 JSON。
Claude Code hook 可以阻擋工具呼叫嗎?
可以。將 {"decision": "block", "reason": "explanation"} 輸出至 stdout 的 hook,會阻止該工具呼叫執行。這是安全 hook 的核心機制:我的 git-safety-guardian 阻擋對 main 的強制推送、credentials-check 阻擋讀取 .env 檔案,而 recursion-guard 則阻擋過度的代理生成。這種阻擋是確定性的,無法透過提示繞過。以退出碼 0 靜默結束的 hook,則允許操作繼續進行。
PreToolUse 與 PostToolUse hook 有什麼差別?
PreToolUse 在工具執行前觸發,可完全阻擋該操作。請用它作為安全閘門:阻擋危險指令、檢查憑證、強制執行生成預算。PostToolUse 則在工具完成後觸發,可提供回饋或引發後續動作。請用它進行驗證:檢查輸出品質、記錄活動、驗證共識。兩者共同構成四層安全網中的第一層與第三層:預防(PreToolUse)、上下文注入(UserPromptSubmit)、驗證(PostToolUse),以及品質把關(Stop)。
References
-
Author’s hook infrastructure. 95 hooks across 6 of the 26 available lifecycle events (v2.1.116, April 2026), developed over 9 months (2025-2026). ↩
-
Anthropic, “Claude Code Hooks Reference,” 2026. 26 lifecycle event types as of v2.1.116 (April 2026). ↩
-
Anthropic, “Claude Code Documentation,” 2025. Hook input/output JSON format. ↩
-
Author’s git-safety-guardian.sh. 8 intercepted force-push attempts tracked in
~/.claude/state/. ↩ -
Author’s recursion-guard.sh. Budget inheritance model documented in
~/.claude/configs/recursion-limits.json. ↩ -
Author’s config-driven architecture. 14 JSON config files encoding all hook thresholds and rules. ↩
-
Anthropic, “Claude Code Documentation,” 2025. Hook configuration and async execution. ↩
-
Anthropic, “Claude Code Documentation,” 2025. Hook scope hierarchy. ↩