← 所有文章

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.shrecursion-guard.shcredentials-check.sh
PostToolUse 工具执行完成之后 post-deliberation.shlog-bash.sh

响应事件

事件 触发时机 我的Hook
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


起源故事:最重要的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

作用域层级

  1. 用户级别~/.claude/settings.json)— 适用于所有项目。我的95个hook存放在这里。
  2. 项目级别.claude/settings.json)— 添加项目特定的hook。
  3. 技能/子代理前置声明 — 作用域限定在特定组件的生命周期内。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日志,识别值得自动化的新模式


参考文献


  1. Author’s hook infrastructure. 95 hooks across 6 lifecycle events, developed over 9 months (2025-2026). 

  2. Anthropic, “Claude Code Documentation,” 2025. Hook lifecycle events. 

  3. Anthropic, “Claude Code Documentation,” 2025. Hook input/output JSON format. 

  4. Author’s git-safety-guardian.sh. 8 intercepted force-push attempts tracked in ~/.claude/state/

  5. Author’s recursion-guard.sh. Budget inheritance model documented in ~/.claude/configs/recursion-limits.json

  6. Author’s config-driven architecture. 14 JSON config files encoding all hook thresholds and rules. 

  7. Anthropic, “Claude Code Documentation,” 2025. Hook configuration and async execution. 

  8. Anthropic, “Claude Code Documentation,” 2025. Hook scope hierarchy.