← 返回教程库

Hooks——用确定性脚本给 Agent 上护栏

最后更新 2026-06-25
你将学到
  • 理解 Hook 机制的核心价值:用确定性脚本填补 AI 概率性执行的空隙
  • 掌握 Claude Code Hooks 的触发时机和配置格式,能写出可运行的 hook 配置
  • 通过两个真实例子(格式化 hook、危险操作拦截 hook)建立落地感
  • 区分 Hook 和规则文件(CLAUDE.md / .cursorrules)的适用场景,知道什么时候该用哪个

有个场景你可能遇到过:在 CLAUDE.md 里写了"改完代码一定要跑 lint",结果 Claude Code 有时跑,有时忘,有时说"这次改动很小,不需要跑"——然后 CI 挂了。

这不是 AI 偷懒,这是 AI 的本质决定的。AI 是概率性的,提示词是建议,不是合同。你加多少个"一定""必须""务必",也不能保证每次都执行。

Hooks 解决的就是这个问题:把"应该做的事"从提示词里拿出来,变成由你写的脚本在固定时机强制触发。脚本不会忘,不会判断"这次情况特殊",不会被上下文带偏。


Hooks 是什么

Hook 是 Agent 工具(以 Claude Code 为代表)在执行过程中暴露出来的脚本触发点。当 Agent 到达某个特定节点,工具会自动调用你预先配置的命令或脚本,不需要 AI 主动"记得"去做。

概念上和 git hooks 很像:你在 .git/hooks/pre-commit 放一个脚本,每次 commit 前它就跑——不管你这次 commit 是大改还是改一个标点,脚本都跑。Claude Code Hooks 的思路完全一样,只不过触发时机变成了 AI Agent 的操作节点。

常见的 hook 触发场景包括:

  • 改完文件之后 → 自动格式化(prettier / gofmt / black)
  • 提交代码之前 → 强制跑 lint 和测试,不通过就阻断
  • 执行某类工具之前 → 检查操作是否涉及敏感路径,危险的直接拦截
  • Agent 会话结束时 → 记日志、发通知、清理临时文件

核心价值在于:你写的脚本是确定性的,AI 是概率性的。 把必须做的事交给脚本,把需要判断的事交给 AI,各司其职。


为什么光靠提示词不够

CLAUDE.md 和 cursor rules 里写规则是第一道防线,但有天然上限。

提示词的问题:

  1. AI 会推理。"这次只改了注释,lint 应该没问题"——这个推理在它看来完全合理,所以跳过了。
  2. 上下文越长越容易漂移。一个 50 轮对话里,第 3 轮写的"记得跑测试"到第 40 轮可能已经不在 AI 的注意力范围内。
  3. 规则是建议,不是约束。你没有办法"强制" AI 通过提示词做某件事,它只能尽力遵守。

换句话说:提示词管的是意图,Hook 管的是行为。两者不是替代关系,而是互补的——规则文件说"你应该干什么",Hook 确保"这件事必然发生"。


Claude Code 的 Hook 机制

触发时机

Claude Code 目前支持以下几个 hook 时机(以官方文档为准,版本迭代可能调整):

Hook 名称 触发时机
PreToolUse Agent 调用任何工具之前(比如写文件、运行命令)
PostToolUse Agent 调用任何工具之后
Notification Claude Code 发送通知时(如等待用户输入)
Stop Agent 完成任务、准备退出时

其中 PreToolUsePostToolUse 是最常用的,因为 Claude Code 的核心操作(写文件 Write、执行命令 Bash、编辑 Edit)都是工具调用,可以在这两个时机拦截或后处理。

拦截逻辑:如果你的 hook 脚本退出码不为 0,Claude Code 会把脚本的输出当作错误信息反馈给 AI,让它知道"这个操作被拒绝了,原因是 XXX"。这是实现"危险操作拦截"的关键机制。

配置位置

Hook 写在项目级配置或用户级配置里。以 Claude Code 为例,配置文件路径:

  • 项目级.claude/settings.json(跟着项目走,适合团队共用)
  • 用户级~/.claude/settings.json(本地生效,适合个人偏好)

配置格式如下(以官方文档格式为准):

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "your-script.sh"
          }
        ]
      }
    ]
  }
}

字段说明:

  • matcher:用正则匹配工具名,决定这个 hook 响应哪些工具调用
  • type:目前主要是 command,即运行一条 shell 命令
  • command:要执行的命令,可以是脚本路径,也可以是内联命令

两个真实 Hook 例子

例子 1:改完文件自动格式化

场景:项目用 Prettier 做格式化。每次让 Claude Code 改文件,都要提醒它"改完 prettier 格式化一下",时不时忘、时不时格式不一致,PR 里一堆格式 diff。

目标:Claude Code 用 WriteEdit 改完文件,自动对改动的文件跑 Prettier,不需要 AI 记得,也不需要你手动跑。

配置.claude/settings.json):

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\" 2>&1 || true"
          }
        ]
      }
    ]
  }
}

说明几个细节:

  • $CLAUDE_TOOL_INPUT_FILE_PATH 是 Claude Code 传给 hook 的环境变量,代表刚才被修改的文件路径。可用变量清单以官方文档为准。
  • || true 确保 Prettier 报错时(比如文件类型不支持)hook 不影响正常流程——格式化失败不应该中断 Claude Code 的工作。
  • 这个 hook 是 PostToolUse,在文件写完之后触发,对改动内容不做拦截。

效果:Claude Code 每次写文件,Prettier 在后台默默跑一遍,代码风格始终统一,你的 diff 里再也没有格式噪音。


例子 2:拦截危险的删除操作

场景:有一个重要的配置目录 config/prod/,绝对不能让 AI 误删。让 Claude Code 做清理任务时,偶尔它会把这个目录一起删掉,因为它认为"这是旧配置"。

目标:在 Claude Code 执行任何 Bash 命令之前,检查命令里是否涉及 config/prod/,如果有删除操作就立刻拒绝。

拦截脚本scripts/guard-prod-config.sh):

#!/bin/bash
# 守护 config/prod 目录,拦截涉及该目录的删除操作
# Claude Code 通过环境变量传入命令内容:CLAUDE_TOOL_INPUT_COMMAND

COMMAND="${CLAUDE_TOOL_INPUT_COMMAND}"

# 检查是否是删除操作,且涉及 config/prod
if echo "$COMMAND" | grep -qE '(rm|rmdir|git rm|unlink)' && \
   echo "$COMMAND" | grep -q 'config/prod'; then
  echo "[HOOK BLOCKED] 检测到对 config/prod/ 的删除操作,已拦截。"
  echo "如确需删除,请绕过 hook 手动执行,或在 ops 频道确认后操作。"
  exit 1  # 非零退出码 = 拒绝这次工具调用
fi

exit 0  # 放行

配置.claude/settings.json):

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash scripts/guard-prod-config.sh"
          }
        ]
      }
    ]
  }
}

关键点

  • PreToolUse + 退出码 1 = 硬拦截,Claude Code 不会执行这条命令,并把错误信息反馈给 AI。
  • AI 收到错误信息后会解释"为什么被拦截",通常会重新想方案而不是强行绕过。
  • 脚本里的注释是给人看的,解释清楚为什么拦截、怎么绕过(留了正规出口,不是死堵)。

这个模式也可以扩展到:拦截对 .env 文件的读取、阻止 DROP TABLE 这类 SQL、禁止访问 /etc/ 下的系统文件。


Hook 和规则文件的区别

很多人开始时把这两个混用,用规则文件写强制要求、用 hook 写建议——两者用反了。

维度 规则文件(CLAUDE.md / .cursorrules) Hooks
本质 提示词,AI 会读、会理解、会遵守(大概率) 脚本,工具链强制触发,不经过 AI
强制性 软约束,AI 可以"推理"跳过 硬约束,脚本跑不过就是跑不过
适用内容 代码风格偏好、回复语言、任务流程 lint、格式化、安全检查、危险拦截
出错后果 AI 忘了执行,你发现了再补救 操作被拒绝,AI 看到错误信息重新想方案
维护成本 低,自然语言写 中,需要写和维护脚本

判断用哪个的简单规则

  • 这件事如果 AI 偶尔没做,能接受吗?能接受 → 规则文件不能接受 → Hook
  • 这件事依赖具体环境(比如"文件路径存不存在")?依赖 → Hook 里用脚本判断不依赖 → 规则文件写清楚就行

具体的规则文件写法和项目记忆,参考 如何写 CLAUDE.md 和 cursor rules。代码质量和安全审查的整体思路,见 AI 代码质量与安全 review


故障排查表

症状 可能原因 排查方向
Hook 脚本一直不触发 matcher 写错,没匹配到工具名 "matcher": ".*" 先匹配所有工具,确认 hook 框架本身生效;再缩小 matcher
Hook 触发了但环境变量是空 变量名拼错,或该 hook 时机不传这个变量 查官方文档确认对应时机可用的变量;在脚本里 `env
脚本退出码是 1,但 Claude Code 没有拦截 用的是 PostToolUse 而非 PreToolUse 拦截必须用 PreToolUse,后置 hook 的退出码不阻断操作
Hook 脚本报"Permission denied" 脚本没有可执行权限 chmod +x scripts/your-hook.sh;Windows 下注意行尾符(CRLF 会导致 bash 解析失败)
格式化 hook 把文件改乱了 Prettier 版本和项目配置不匹配 先在项目根目录手动跑 npx prettier --write src/ 验证格式化结果符合预期,再加 hook
拦截 hook 误杀正常操作 正则太宽泛 在脚本里加调试输出 echo "COMMAND: $CLAUDE_TOOL_INPUT_COMMAND",看实际触发时命令内容,收窄匹配条件

工程实践建议

把 hook 脚本版本化。放在项目的 scripts/.claude/hooks/ 目录下,纳入 git 管理。hook 脚本是项目工程规范的一部分,应该跟着代码走,不应该只活在本地。

|| true 再去掉。新写一个 hook 时,先加 || true 让它不影响流程,跑几天观察触发情况和输出,确认逻辑符合预期后再去掉,变成真正的阻断。直接上拦截 hook 容易误杀正常操作。

不要在 hook 里做重活。Hook 是同步触发的,脚本跑多久 Claude Code 就等多久。格式化单个文件、跑几条 lint 规则可以;完整跑测试套件、拉取远程数据不合适——那些放 CI 里。

配合 SDD 工作流。在 规格驱动开发(SDD) 里,Hooks 是"实施阶段"的安全网:规格文件定义了"做什么",Hooks 保证"不做什么"(不破坏生产配置、不跳过格式化)。两者配合,才是真正有工程纪律的 AI 编程流程。


常见问题

Q:我用 Cursor 而不是 Claude Code,也有类似机制吗?

Cursor 目前没有原生的 Hook 机制,但可以结合 git hooks 和 .cursorrules 来部分实现——在 .cursorrules 里写强规则,配合 pre-commit hook 做 lint/格式化。效果不如 Claude Code Hooks 彻底,但也比纯靠提示词可靠。

Q:hook 脚本可以调用 AI 吗?比如让另一个 AI 来审查?

技术上可以,但要慎重:每次工具调用都触发 AI 审查,速度会很慢,成本也高。实际项目里,hook 里调用 AI 适合用在高风险操作的二次确认(比如批量删除前用 AI 解释一遍操作意图),而不是每次写文件都触发。

Q:团队多人用同一个项目配置,hook 脚本里的路径怎么处理?

用相对路径或脚本所在目录的 $0,避免硬编码绝对路径。如果 hook 依赖本地工具(比如 prettier 必须在本地 node_modules 里),在项目 README 或 CLAUDE.md 里写清楚依赖前置条件,否则新同学 clone 项目后 hook 莫名其妙报错,不知道去哪查。

Q:可以动态决定 hook 的行为吗?比如生产环境拦截、开发环境放行?

可以。脚本里读环境变量 $NODE_ENV 或自定义的 $CLAUDE_ENV,分支处理。配置文件里设置对应的环境变量,或在 hook 命令里传参。比如:NODE_ENV=production bash scripts/guard-prod-config.sh


👉 看看我们的 AI 编程实战体系课,或逛 AI 编程教程大全 把基本功打扎实。

📄 来源 / 自校链接

本文为学习整理,关键步骤与代码请结合下列官方来源验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为学习与落地整理,AI 工具与平台更新较快,关键步骤请结合官方最新资料验证。见免责声明