用 Hooks 给 Claude Code 加规矩

Claude Code Hooks 允许你在 Claude Code 运行的每个关键时机(调用工具前后、提交 prompt、想结束对话等)触发你预先配好的脚本,从而实现对 Claude Code 行为的精细控制。

不过说实话,目前模型的指令遵从能力已经很强了,hooks 更多的是一种能力扩展,大部分时候没必要使用,所以大家了解一下这个功能即可。

Claude Code 的常用技巧 里讲过用 CLAUDE.md 告诉 Claude 你的项目约定,让它「记住」该怎么干活。但 CLAUDE.md 终究是软提醒,Claude 还是会忘,还是会跳过你交代过的步骤,还是会拍着胸脯说「已完成」,但其实代码根本没跑通。

这篇讲的 hooks 可以一定程度上解决这个问题。在 Claude 的每个关键节点(如调用工具前后、提交 prompt、想结束对话时)都会触发你预先配好的脚本,脚本的输出/返回值可以影响 Claude Code 的下一步行动。

说白了,hooks 把那些「指望 Claude 自觉」的约定,变成了系统层面的强制。

hooks 写在 settings.json 里,跟权限设置在同一个文件。主要有两个位置:

  • 用户级 :~/.claude/settings.json,所有项目都生效,适合放跨项目通用的规则(比如拦危险命令)。
  • 项目级 :项目根目录下的 .claude/settings.json,可以跟着项目走,团队共享。建议把跟具体项目相关的 hook(比如 git commit 规范等)放这里。

两个文件里的 hook 会同时生效,不会互相覆盖。

最小可用配置

先看一个完整最小例子,感受一下结构:

复制代码
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Claude 改了一个文件!' >&2"
          }
        ]
      }
    ]
  }
}

把这段加到 ~/.claude/settings.json,重启 Claude Code,让它改个文件,你会在它的输出里看到这条消息。

注意结构是两层嵌套 的 hooks:

  • 外层 hooks 的 key 是事件名(比如 PostToolUse)。
  • 内层是一个数组,每个元素是一组 { matcher, hooks }。
  • matcher 用来过滤工具名,匹配上才触发内层的 hooks。
  • 每个具体 hook 都要写 type 字段,告诉 Claude Code 这条 hook 该怎么执行。

type 字段官方支持 五种取值:

  • command:跑一条 shell 命令,用退出码或 stdout 控制后续行为,最常用
  • http:把 hook 输入 POST 到一个 HTTP 端点
  • mcp_tool:调用某个已连接的 MCP server 上的工具
  • prompt:把判定条件交给一个 LLM 单轮评估,让它返回 JSON 决定放行还是拦截
  • agent:起一个子 agent 来做验证

本文的例子全用 command,其他四种不展开。其中 prompt 类型比较特别,它不跑脚本而是让模型来判定条件,Claude Code 的 /goal 命令就是基于它实现的,原理可以看/goal 命令是怎么实现的。

同一个事件下可以配多个 hook,匹配上的会并行执行,不是串行短路。只要其中一个 hook 拦下动作,其他 hook 即便放行也救不回来,所以做安全拦截不用担心被别的 hook 绕过。

matcher 是带有通配符的字符串,主要用来匹配工具名,它的几种写法:

matcher 值 含义
省略 / "*" / "" 匹配所有
"Bash" 精确匹配 Bash 工具
`"Edit Write"`
"mcp__memory__.*" 含其他字符时按 JS 正则匹配

要按 Bash 子命令过滤(比如只在 git commit 时触发)的话,不能用 matcher,要用单独的 if 字段:

复制代码
{
  "matcher": "Bash",
  "hooks": [
    {
      "type": "command",
      "if": "Bash(git commit *)",
      "command": "..."
    }
  ]
}

每次改完 settings.json 都需要重启 Claude Code 才生效。

后面例子里所有脚本我会统一放在 .claude/hooks/ 目录下,但这只是约定,放别处也可以,配置里路径写对就行。

小技巧

嫌手写 settings.json 麻烦其实可以偷懒。直接在 Claude Code 里跟 Claude 说「帮我加一个 PostToolUse hook,每次写完文件跑代码格式化工具」之类的话,让它调用内置的 /update-config skill 查询相关的配置方法,然后帮你改配置文件,写完还会自动校验格式。

① 表面问题:这些配置和脚本到底在干什么?

你看到的是:

复制代码
PreToolUse、matcher、command、stdin、exit 2......

它们其实分别回答几个很普通的问题:

什么时候做?检查哪种操作?运行哪个程序?给它什么信息?检查完怎么办?

文章一上来展示所有配置,就像还没讲清楚交通灯的作用,先让你看接线图。

② 实际问题:怎样让一条要求在操作发生时真正被执行?

假设你告诉 Claude:

"提交代码之前,先运行检查。"

这里有两件不同的事:

  • 理解要求:模型读懂这句话,规划接下来做什么。
  • 落实检查:软件在提交发生之前,确实运行检查,并处理结果。

CLAUDE.md 主要帮助模型理解和遵循约定。Hook 则可以把检查接到执行流程里。

理解这层区别,就能明白文章为什么说"给 Claude Code 加规矩"。

③ 拆开概念:到底是谁做什么?

组件 用普通话解释
Claude 模型 思考下一步,提出"我要提交代码"
Claude Code 承载模型、调度工具、运行 Hook 的软件
工具 真正执行操作的能力,例如运行终端命令、修改文件
Hook 事件 软件预留的触发时机,例如"调用工具之前"
Hook 配置 指定在哪个时机、满足什么条件、运行什么
Hook 脚本 实际检查或处理事情的小程序

对于文章里的 command 类型 Hook,触发检查的是 Claude Code;检查逻辑写在脚本里。 正常配置生效并匹配事件时,不需要模型每次主动想起来调用它。

所以,这三者的关系是:

复制代码
模型提出操作 → Claude Code 调度 → 工具执行操作
                       ↓
                指定时机运行 Hook

④ 有哪些办法让"提交前检查"发生?

方案 怎样工作 优点 成本与局限
写进 CLAUDE.md 模型读到要求后主动运行检查 简单,适合解释约定和工作方法 可能漏做,要核对执行记录
配置提交前 Hook Claude Code 在匹配的操作前自动运行检查 检查可重复,也能阻止这次操作 需要维护脚本,会增加等待时间,配置错误可能漏拦或误拦

两者可以配合:用文字说明"为什么要检查",用 Hook 执行明确的检查条件。

规则文件和脚本都可以版本化、共享;但 Hook 会运行真实程序,所以要明确它能读写什么、是否访问外部服务。验证时应分别模拟"检查通过"和"检查失败",确认操作是否按预期继续。出现持续误拦或检查器故障时,应修正规则或停用对应 Hook。

⑤ 现在选择怎么理解?

我建议先理解 PreToolUse:工具执行前的检查关口。

它最容易说明 Hook 的核心:操作还没发生,所以检查结果能够决定是否让它发生。

自动格式化、注入上下文、结束前检查,都可以等这个流程弄懂以后再看。

⑥ 机制是什么?把一次提交慢放

假设模型准备通过终端工具执行:

复制代码
git commit -m "修复登录问题"

流程如下:

复制代码
```mermaid
flowchart TD
    A[模型请求提交代码] --> B[Claude Code 暂缓执行工具]
    B --> C[触发匹配的 PreToolUse Hook]
    C --> D[脚本运行代码检查]
    D --> E{检查通过了吗}
    E -->|通过| F[Hook 不阻拦,继续权限与执行流程]
    E -->|失败| G[阻止本次工具调用,把错误反馈给模型]
    G --> H[模型根据反馈尝试修复]
```

这里最关键的是:检查失败时,这次提交命令还没有执行。

再把配置翻译成人话:

复制代码
PreToolUse
    → 在工具执行之前

matcher: Bash
    → 关注运行终端命令的工具

if: Bash(git commit *)
    → 进一步筛选出提交代码的操作

type: command
    → 用运行本地命令的方式执行 Hook

command: 某个检查脚本
    → 真正运行这个脚本

脚本怎样知道"现在准备做什么"?Claude Code 会通过 stdin,也就是程序的输入通道,给它一份 JSON 数据:

复制代码
{
  "tool_name": "Bash",
  "tool_input": {
    "command": "git commit -m \"修复登录问题\""
  }
}

JSON 在这里就是一张结构化的"操作说明单"。

脚本检查后,再返回处理结果。对于这个前置检查的简单用法:

  • exit 0,且没有额外拒绝指令:脚本执行成功,这道 Hook 不阻拦。
  • exit 2:阻止本次操作。
  • stderr:错误信息通道,可以把"哪项检查失败"反馈给模型。

注意,Hook 不阻拦,不代表所有权限都自动放行,也不代表命令一定执行成功。

失败反馈能帮助模型修复,但不能保证模型一定修得好。遇到缺少依赖、外部服务故障等问题,需要明确报告原因,避免无限重试。

文章剩下的例子,只是在换触发位置和要做的事:

时机 可以安排什么
PreToolUse:工具执行前 检查,不符合条件就拦截
PostToolUse:工具成功执行后 格式化刚改好的文件;不能阻止已经发生的修改
UserPromptSubmit:用户提交消息后、模型处理前 补充当前分支等信息
Stop:模型准备结束这一轮时 检查是否还有需要继续处理的事项
SessionStart:会话开始或恢复时 准备会话需要的上下文

⑦ 同一领域的类比:网页按钮的点击事件

网页可以配置:

复制代码
用户点击"保存" → 自动执行保存函数

Claude Code 可以配置:

复制代码
模型准备调用工具 → 自动执行检查脚本

它们共同的机制都是:先登记"发生什么时执行什么",等事件发生后,由软件触发。

⑧ 生活里的类比:出厂质检

可以把模型想成准备发货的工作人员:

  • CLAUDE.md:工作手册写着"发货前检查"。
  • PreToolUse:货物离厂前的检查站。
  • Hook 脚本:具体的质检程序。
  • 检查结果:放行或退回,并说明原因。

但质检站只能检查你规定的项目。只检查重量,不能证明颜色、尺寸也正确。

同样,测试通过只能证明已检查的条件通过,不能证明整个需求已经完成。 原文把 Hook 描述成让模型无法"假装完成",说得过满了。

⑨ 证据、边界和下一步

我已对照你贴的文章与 Claude Code 官方 Hooks 文档:事件触发、脚本接收输入、前置 Hook 阻止工具调用,这条核心机制成立。但原文部分细节存在版本差异,例如"exit 2 时忽略所有 JSON 输出"已不符合当前文档,配置时需要查对应版本。

相关推荐
摩尔元数42 分钟前
制造业AI落地难?摩尔元数用智能体破局
人工智能
Data-Miner1 小时前
人工智能怎么做数据分析?从准备到出报告的实操指南
人工智能·数据挖掘·数据分析
数字会议深科技1 小时前
会议方案设计应如何融入 AI 和物联网等领先会议技术
人工智能·物联网·会议解决方案·会议设计方案
Alinket1 小时前
不训练模型也能加 AI:5 套可直接量产的组合方案
人工智能·物联网·ai·健康医疗·无线通信·智能硬件·iot
林伽一1 小时前
推理链路与资本成本同时收紧,AI 工程进入算细账阶段|2026年10月21日
人工智能·科技·ai·架构
天远API1 小时前
零信任架构实战:基于天远运营商二要素V即时版构建自动化分时租赁出行会员身份一致性审核网关
运维·人工智能·架构·自动化
薛定e的猫咪1 小时前
(ICLR 2020)MAML 靠的不是快速学习,而是特征复用
人工智能·深度学习·学习·算法·机器学习
海边捡石子2 小时前
从零设计一个通用智能电话平台:FreeSWITCH + ESL + AI 实战架构
人工智能·架构·webrtc