Claude Code Hooks:我的95个Hook每一个存在的理由
我为Claude Code构建了95个hook。每一个的存在都源于某次事故。git-safety-guardian的诞生是因为Claude曾强制推送到main分支。recursion-guard的诞生是因为一个子代理产生了无限子进程。blog-quality-gate的诞生是因为我发布了一篇包含7个被动语态句子和一个悬空脚注的文章。1
TL;DR
Claude Code hooks在AI辅助开发的特定生命周期节点执行shell命令。Hook在概率性系统(LLM)之上提供确定性保障(阻止危险的git命令、注入上下文、强制执行质量标准)。在我的基础设施中构建了95个hook之后,我发现最好的hook来自事故,而非规划。本文涵盖了架构设计、最关键hook背后的起源故事,以及我在9个月hook开发中总结的模式。
架构设计
Claude Code暴露了生命周期事件,hook可以在这些节点拦截、修改或阻止行为:2
会话事件
| 事件 | 触发时机 | 我的Hook |
|---|---|---|
| SessionStart | 新会话开始时 | session-start.sh — 注入日期、验证虚拟环境、初始化递归状态 |
| 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令牌。在注意到令牌消耗加速后,我手动终止了会话。
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个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、质量门禁、审阅者停止门禁。
每一层都是独立的。如果一个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工具调用时触发的匹配器。异步hook(async: true)在后台运行,不会阻塞。7
作用域层级
- 用户级别(
~/.claude/settings.json)— 适用于所有项目。我的95个hook存放在这里。 - 项目级别(
.claude/settings.json)— 添加项目特定的hook。 - 技能/子代理前置声明 — 作用域限定在特定组件的生命周期内。8
回顾:我会做出的改变
从3个hook开始,而不是25个。 我第一个月产出了25个hook,其中许多添加的上下文是代理本身已经具备的。在每次工具调用时加载25个hook的开销是可以被感知到的。我最终精简到真正产生价值的hook(预防了真实事故、捕获了真实的质量问题)。
从第一天就采用配置驱动。 我花了两周时间将硬编码阈值重构为JSON配置。如果一开始就使用配置,这次重构的成本本可以为零。
尽早建立测试基础设施。 最初的20个hook没有测试。当我添加测试框架(48个bash集成测试)时,发现了3个在边界情况下静默失败的hook。测试本应随第1个hook一同交付。
核心要点
给刚开始使用hook的开发者: - 从三个hook开始:git安全(PreToolUse:Bash)、上下文注入(UserPromptSubmit)和质量门禁(Stop);只有在有事故证明其必要性时才添加更多 - 使用决策时机框架:hook架构是不可逆的(95个hook依赖于它),因此在编写hook之前,先投入精力设计生命周期模型
给正在标准化hook的团队: - 在用户级别标准化hook,确保每个团队成员获得相同的安全防护 - 跟踪hook指标(阻止的操作、拦截的事故)以证明维护成本的合理性 - 每月审查hook日志,识别值得自动化的新模式
参考文献
-
Author’s hook infrastructure. 95 hooks across 6 lifecycle events, developed over 9 months (2025-2026). ↩
-
Anthropic, “Claude Code Documentation,” 2025. Hook lifecycle events. ↩
-
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. ↩