Claude Code Hooks:我的95个钩子,每一个都有存在的理由
我为Claude Code构建了95个钩子。每一个的存在,都源于某次出错的经历。git-safety-guardian之所以存在,是因为Claude曾强制推送到main分支。recursion-guard之所以存在,是因为某个子代理生成了无限递归的子代理。blog-quality-gate之所以存在,是因为我曾发布过一篇包含7个被动语态句子和一个悬空脚注的文章。1
Claude Code钩子是在特定生命周期节点(会话启动、工具使用前后、提示提交、响应完成)执行的shell命令,为概率性LLM行为之上提供确定性的防护栏。钩子通过stdin接收JSON,通过stdout返回决策(阻止、允许或修改)。它们负责执行诸如阻止强制推送到main、防止递归代理生成、为每个会话注入上下文、把控LLM自身无法保证的输出质量等策略。
TL;DR
Claude Code钩子在AI辅助开发的特定生命周期节点执行shell命令。钩子在概率性系统(LLM)之上提供确定性保障(阻止危险的git命令、注入上下文、强制质量标准)。在我的基础设施中构建了95个钩子之后,我发现最好的钩子来自于真实事件,而非规划。本文介绍架构本身、最关键钩子背后的起源故事,以及9个月钩子开发中总结出的模式。
架构
Claude Code暴露了26个生命周期事件,钩子可以在这些节点拦截、修改或阻止行为(截至v2.1.116,2026年4月)。2我的钩子主要针对其中6个最常用的事件:
会话事件
| 事件 | 触发时机 | 我的钩子 |
|---|---|---|
| SessionStart | 新会话开始 | session-start.sh:注入日期、验证venv、初始化递归状态 |
| SessionEnd | 会话终止 | 清理临时文件 |
工具执行事件
| 事件 | 触发时机 | 我的钩子 |
|---|---|---|
| PreToolUse | 任何工具执行前 | git-safety-guardian.sh、recursion-guard.sh、credentials-check.sh |
| PostToolUse | 工具完成后 | post-deliberation.sh、log-bash.sh |
响应事件
| 事件 | 触发时机 | 我的钩子 |
|---|---|---|
| UserPromptSubmit | 用户发送提示 | 上下文注入器(日期、分支、模型信息) |
| Stop | Claude完成响应 | deliberation-pride-check.sh、reviewer-stop-gate.sh |
每个钩子通过stdin接收JSON,通过stdout进行通信:
{"decision": "block", "reason": "Force push to main is prohibited"}
或者以代码0静默退出允许执行。3
起源故事:最重要的那些钩子
钩子1:git-safety-guardian.sh(PreToolUse:Bash)
事件经过:在一次早期的Claude Code会话中,我让代理”清理一下git历史”。代理执行了git push --force origin main。强制推送覆盖了共享分支上三天的提交。我从本地备份中恢复了,但长达4小时的恢复过程让我确信:概率性判断绝不应掌控破坏性的git操作。
钩子:
#!/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
经验教训:钩子并不试图理解意图。它只对命令字符串做模式匹配。简单、确定、无法通过巧妙的提示绕过。代理仍然可以强制推送到功能分支(有时是合理的),但main/master被永久保护。4
累计拦截:9个月内拦截了8次强制推送尝试。
钩子2:recursion-guard.sh(PreToolUse:Task)
事件经过:在构建推敲系统时,我运行了一个会话,它生成了3个探索子代理。每个子代理因缺少生成限制,又各自生成了自己的子代理。递归以10倍于正常速率消耗APItokens。我注意到token消耗加速后手动终止了会话。
钩子:
#!/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次失控的生成尝试。
钩子3:blog-quality-gate.sh(Stop)
事件经过:我发布过一篇博客,其中有7个被动语态句子、一个在正文中引用但在参考资料部分缺失的脚注,开篇第一句是”was implemented by the team”。文章在编辑器里看起来很精致,却通不过任何人类审稿人都能抓到的基础质量检查。
钩子:对任何修改过的博客内容文件运行我的12模块博客linter。检查被动语态、悬空脚注、缺失的meta描述、未标记语言的代码块、引用完整性。每条发现都具体明确:”第47行:检测到被动语态’was implemented by the team’。建议:’the team implemented’。”
与人类反馈的类比:钩子批评作品本身,而不是作者。它说的是”第47行有被动语态”,而不是”你写得不好”。让人类反馈具建设性的原则,同样让自动化反馈变得有用。
95个钩子背后的模式
配置驱动架构
我的钩子从硬编码值演化为配置驱动的行为:
~/.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个钩子形成了一张四层安全网:
第一层:预防(PreToolUse):在坏事发生前阻止它。git-safety-guardian、credentials-check、recursion-guard。
第二层:上下文(UserPromptSubmit、SessionStart):注入代理所需的信息。日期、分支、项目上下文、记忆条目、哲学原则。
第三层:验证(PostToolUse):验证已完成的操作是否符合标准。post-deliberation共识检查、输出日志。
第四层:质量(Stop):把控最终输出。pride check、quality gate、reviewer stop gate。这一层实现了元认知监控——代理评估自己推理质量本身,而不仅仅是输出。
每一层都独立运作。如果PreToolUse钩子静默失败,Stop钩子仍会捕捉质量问题。纵深防御,应用于AI代理行为。
配置
钩子定义在.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字段筛选哪些工具会触发钩子。PreToolUse:Task是一种简写,表示只在Task工具调用时触发的匹配器。异步钩子(async: true)在后台运行而不阻塞。7
作用域层级
- 用户级(
~/.claude/settings.json):适用于所有项目。我的95个钩子都在这里。 - 项目级(
.claude/settings.json):添加特定于项目的钩子。 - Skill/Subagent frontmatter:作用域为特定组件的生命周期。8
如果重来,我会做什么不同
从3个钩子开始,而不是25个。我的第一个月产出了25个钩子,其中许多注入的上下文是代理本就拥有的。每次工具调用都要加载25个钩子的开销是可测量的。我最终精简到了真正有价值的那些钩子(阻止过真实事件、捕捉过真实质量问题的)。
从第一天起就配置驱动。我花了两周把硬编码阈值重构为JSON配置。如果一开始就用配置,这次重构本是免费的。
尽早建立测试基础设施。前20个钩子没有测试。当我加上测试框架(48个bash集成测试)时,发现3个钩子在边界情况下静默失败。测试本应与钩子#1一同发布。
核心要点
对刚开始使用钩子的开发者: - 从三个钩子起步:git安全(PreToolUse:Bash)、上下文注入(UserPromptSubmit)和质量把关(Stop);只有在出现值得增加的事件时才添加更多 - 运用决策时机框架:钩子架构是不可逆的(95个钩子都依赖于它),因此在编写钩子之前要先在生命周期模型上下功夫
对要统一钩子规范的团队: - 在用户级标准化钩子,让每位团队成员都有相同的安全防护 - 跟踪钩子指标(被阻止的操作、拦截的事件),以证明维护成本的合理性 - 每月审阅钩子日志,发现值得自动化的新模式
FAQ
什么是Claude Code钩子?
钩子是在Claude Code会话的特定生命周期节点执行的shell脚本。它们在特定时刻确定性地触发:工具运行前(PreToolUse)、工具完成后(PostToolUse)、您发送提示时(UserPromptSubmit)、会话开始时(SessionStart)、Claude完成响应时(Stop)。钩子通过stdin接收带有完整上下文(工具名称、输入、会话ID)的JSON,可以阻止操作、注入上下文或强制质量关卡。它们在概率性LLM之上提供确定性保障。
Claude Code可以运行多少个钩子,是否有性能开销?
钩子数量没有硬性上限。我在26个可用生命周期事件中的6个上运行95个钩子(截至v2.1.116,2026年4月),每个事件总开销约200毫秒。实际限制在于延迟:每个钩子都会在工具调用前后增加执行时间。关键优化是使用分发器(每个事件一个,从缓存的stdin中顺序运行钩子),而不是各自独立读取stdin的钩子。在我早期的配置中,独立钩子的并发写入导致了JSON损坏;分发器彻底消除了这种失效模式。
Claude Code钩子会在哪些事件上触发?
截至v2.1.116(2026年4月),Claude Code暴露了26个生命周期事件供钩子使用。我的钩子针对的6个最常用事件是:SessionStart(新会话开始)、SessionEnd(会话终止)、PreToolUse(任何工具执行前)、PostToolUse(工具完成后)、UserPromptSubmit(用户发送提示)、Stop(Claude完成响应)。其他事件还包括SubagentStart、PermissionRequest、PermissionDenied、TaskCreated、CwdChanged、FileChanged、PreCompact等。PreToolUse和PostToolUse还可以通过诸如PreToolUse:Bash或PreToolUse:Task之类的匹配器进一步限定作用域,仅在特定工具类型上触发。每个事件通过stdin接收带有相关上下文的JSON。
Claude Code钩子可以阻止工具调用吗?
可以。向stdout输出{"decision": "block", "reason": "explanation"}的钩子会阻止工具调用的执行。这是安全钩子的核心机制:我的git-safety-guardian阻止对main的强制推送,credentials-check阻止对.env文件的读取,recursion-guard阻止过度的代理生成。阻止是确定性的,无法通过提示绕过。以代码0静默退出的钩子允许操作继续。
PreToolUse和PostToolUse钩子有什么区别?
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. ↩