当 AI Agent 从"辅助生成内容"走向"实际执行操作"时,能力不再是边界,边界本身才是新的能力;
而 Hook — 就是将 Agent 行动权重新拉回工程控制之内的"钩子"。
作为重度Vibe Coding使用者,在使用AI的过程中时常出现失控感,特别是在程序长时间执行过程中,由于中间思考决策过程的缺失,导致Agent申请权限或者更改文件等风险操作时都需要花大量时间来进行double check。
为了让开发者中重新获得掌控感,寻找问题的解决办法,笔者对claude code的hook机制仔细研读,顺便以写教程的方式促进自己的理解,同步也在开发一个插件希望能缓解这种由于失控带来的焦虑。
本帖主要是用来梳理逻辑,构建知识Map,官方文档更全面且详细 如有需要可以自行查阅: Hooks 参考 - Claude Code Docs
一、前言
Agent的概念层出不穷,在正式介绍Hook之前,也许你更在意的是他跟目前广为人知的MCP,Skill之间的差异,为什么我们会需要Hook而不是其他的?这一点很重要,因为明白各个技术的边界将成为你使用工具还是被工具使用最重要的区别:
机制 核心问题 更像什么 Skill Claude 应该如何完成某类任务? 任务说明书 / 工作流知识包 MCP Claude 可以调用哪些外部能力? 工具接口 / 外部能力总线 Hook Claude 执行到某个节点时,必须先经过什么规则? 生命周期拦截器 / 工程治理层总而言之,Hook 像是加在 Agent 执行链路上的一把"工程锁":
它不是为了限制 Agent 的能力,而是为了让 Agent 的每一次关键行动都能被看见、被解释、被约束、被接管。在几乎不牺牲执行灵活性的前提下,Hook 极大增强了工程师对 Agent 行为边界的掌控。
二、什么是Hook?
hook的英文直译就是钩子,如果你之前没接触过事件响应、插件或者复杂框架,第一次看到这个词可能会有点懵:程序里的钩子是什么的?其实可以把 Hook 就是一种 “在特定时机自动执行的函数” 。比如说,某个事件发生了、某个流程结束了、某个状态发生变化了,系统就会自动调用你提前写好的那段代码。就像你提前在这个位置"挂了一个钩子",等程序运行到这里时,这个钩子就会被 连带 触发。作为一种抽象概念hook的思想广泛存在不同领域/场景,例如:
- 前端组件挂载后执行初始化逻辑;
- Git 提交前自动运行格式检查;
- 请求完成后执行回调处理;
- 程序退出前自动保存状态或清理资源。
在Claude Code中也用于有效管理Agent的运行设计了对应的Hook机制,并且贯穿整个 Agent生命周期
与详细的官方文档不同,这篇博文将从下面三个问题出发让你快速理解Hook的整个逻辑,当你需要使用Hook来改善你自己的工作流的时候能快速切入:
当然如果觉得冗长可以直接跳转到 六、实例展示 中根据实际用例来理解Hook的流程!
- When? ------ 三、Hook的触发时机(Trigger)
- What? ------ 四、Hook的匹配机制(Matcher)
- How? ------ 五、Hook的执行逻辑(Handler)
- Go ----- 六、实例展示
三、Hook的触发时机(Trigger)
Claude Code 处理一次用户请求时,并不是简单地"接收输入,然后生成答案"。它会进入一轮完整的 Agent 执行流程:
接收用户输入、理解任务意图、规划操作、调用工具、处理工具结果,并根据结果继续迭代,直到任务完成或中止。
这个流程对应的就是Agent的生命周期,在生命周期的各个阶段都可以使用hook来执行对应的逻辑,下面是官方以流程图的形式可视化了这个过程:
这个图虽然很完善,但是太长了也没有归类,因此从 Agent 运行设计的角度,笔者将hook的触发时机大致的归类为四类分别对应Agent四项基础能力:
- 输入与提示词工程(Prompt Engineering):负责会话初始化、上下文注入、用户输入检查和 Prompt 展开,决定用户请求如何进入 Agent 流程。
- 工具调用控制(Tool Use):负责工具调用前后的权限校验、安全拦截、结果处理和失败处理,决定 Agent 如何安全地执行外部操作。
- 事务分发与子代理编排(Sub-agent / Task Orchestration):负责复杂任务的拆分、分发、子代理启动和任务完成追踪,决定 Agent 如何组织多步骤任务。
- 上下文与状态管理(Context Management):负责上下文压缩、配置变化、文件变化和通知事件,决定 Agent 如何维护运行状态和长期上下文。
目前可设置的触发时机有29个,以输入与提示词工程为例:SessionStart、SessionEnd分别在创建会话以及结束会话时可以调用,适合注入系统提示词/持久化会话内容的功能,UserPromptSubmit、UserPromptExpansion用于优化或者二次处理用户提示。笔者根据自己的理解对目前的29个触发时机进行分类,总结为一图流,并提供一个速查表方便后续查找以及理解:
触发时机&含义解释 速查表 (点击了解更多详细信息)
四、Hook的匹配器(Matcher)
在确定 Hook 的触发时机(Trigger)之后,并不意味着该触发节点下的所有 Hook 都会被执行。Claude Code 还会继续根据 matcher 做一次过滤:只有当前事件满足匹配条件时,对应 Hook 才会被执行。
对于 PreToolUse / PostToolUse 这类工具事件 matcher 匹配的是本次调用的具体工具名,而在SessionStart中匹配的是会话启动方式,Setup 匹配的是触发 setup 的 CLI flag。 具体的触发时机启用的匹配字段需要查看官方文档来确认,官方文档中对每个触发时机的参数都有详细的说明,同样为了方便查找,按照之前的分类进行简单的展示:
Matcher 匹配对象速查表 (点击了解更多详细信息)
由于单个hook包含多种触发条件,因此可以使用正则匹配的方式来自己组合可能的触发情况,具体的操作可以参考官方给出的方式:
写法 含义 示例 省略 / 空字符串 /*
匹配全部
"matcher": "*"
精确匹配
只匹配指定对象
"matcher": "Bash"
多值匹配
用 | 匹配多个对象
"matcher": "Edit | Write"
正则匹配
匹配更复杂的名称模式
"matcher": "mcp__.*__write.*"
五、Hook的执行器(Handler)
在恰当的触发节点,对应的Hook满足了匹配器的触发条件,那么就可以触发后续的执行逻辑,也就是实际的可执行的具体操作
为了保证可扩展性,同时为了维持对MCP,Http,Command的不同响应差异,Claude Code对Hook将执行的操作使用字段来进行区分来适配不同方式独特的需求,官方目前支持了5种不同的类型:
Handler 类型 配置写法 执行方式 适合场景 命令型type: "command"
执行本地 shell 命令或脚本
日志记录、格式化、lint、安全检查、权限拦截
HTTP
type: "http"
将 Hook 输入 JSON 作为 HTTP POST 请求发送到指定 URL
接入远程审计服务、团队策略服务、Webhook 系统
MCP 工具
type: "mcp_tool"
调用已连接 MCP server 上的工具
复用现有 MCP 能力,例如安全扫描、知识库查询、业务系统校验
Prompt
type: "prompt"
向 Claude 模型发送一次提示,进行单轮判断
让模型解释权限原因、判断操作风险、生成轻量决策
Agent
type: "agent"
启动一个 subagent,并允许其使用 Read、Grep、Glob 等工具验证条件后返回结果
更复杂的检查任务,例如跨文件审查、代码变更复核;官方标注为实验性
总的来说,根据实际需求你可以进行下面的判断:
- 如果需要接入外部服务,用
http; - 如果已有 MCP 工具可复用,用
mcp_tool; - 如果需要模型做一次轻量判断,用
prompt; - 如果需要一个能读文件、查代码、综合判断的子代理,用
agent。
六、实例解析(MVP)
在了解了Hook的触发时机(Trigger),匹配机制(Matcher)以及 执行器(Handler)的抽象逻辑之后
笔者介绍一个具体的例子,来介绍实际Claude Code中是如何将上面讲的抽象概念转为可拓展开发的工程实践。只要读懂这个实例那么就掌握了绝大部分Hook的知识。
与 MCP、Skill 类似,Claude Code 也通过配置文件来管理 Hook。Hook 通常写在 settings.json 中,并可以根据配置文件所在位置作用于不同范围(用户级/项目级)。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo 'Claude is about to use Bash' >> ~/claude-hook-demo.log"
}
]
}
]
}
}
下面我们根据之前的总结将 回答三个问题 When? What? How?,这三个问题分别对应上述配置的三个字段
抽象层 直观问题 对应配置 含义 Trigger When:什么时候触发?PreToolUse
在工具调用执行前触发
Matcher
What:匹配什么对象?
"Bash"
只匹配 Bash 工具调用
Handler
How:命中后如何执行?
type: "command" + command
匹配成功后执行一条 shell 命令
PreToolUse决定 Hook 挂在哪个生命周期节点。它表示"工具调用前",所以这个 Hook 会发生在 Bash 真正执行之前。matcher: "Bash"匹配的不是PreToolUse这个事件名,而是本次工具调用的工具名称。也就是说,只有当 Claude Code 即将调用的工具是Bash时,这个 Hook 才会被命中。如果 Claude Code 调用的是Read、Edit、Write等其他工具,这条 Hook 不会触发。hooks数组中的command才是真正的执行逻辑。这里的命令只是写入一条日志,因此它不会改变 Claude Code 的执行结果,只是用于观察 Hook 是否被触发。
上面是一个Log型的Hook程序,如果你希望调用Hook的时候执行其他的那么只需要修改command中的内容即可,甚至你可以再调用claude code来进行分析,下面是笔者正在开发的项目的彩蛋~
{
"hooks": {
"PermissionRequest": [
{
"matcher": "Edit|Write|Bash",
"hooks": [
{
"type": "prompt",
"prompt": "You are explaining a Claude Code permission request to the user. Based on the hook input, explain in Chinese why Claude is requesting this tool permission. Be concise, concrete, and mention the tool name, target file or command if available. Do not approve or deny the request; only explain the reason."
}
]
}
]
}
}
这个示例展示的是一个"模型解释型 Hook"。
它不是用 command 执行固定脚本,而是使用 prompt handler,让模型根据本次权限申请的上下文生成说明。这样,当 Claude Code 申请 Edit、Write 或 Bash 权限时,用户不仅能看到"它要申请权限",还能看到"它为什么需要这个权限"。
例如:
- 如果是
Edit,模型可以说明它准备修改哪个文件、修改目的是什么; - 如果是
Write,模型可以说明它准备新建或覆盖哪个文件; - 如果是
Bash,模型可以说明它准备执行什么命令,以及这个命令大致用于什么操作。
需要注意的是,这个 Hook 只负责解释原因,并不代表自动批准或拒绝权限。最终是否允许执行,仍然由权限机制本身决定。
这个 Hook 的作用是:在高风险工具申请权限时,用模型动态生成一段面向用户的权限解释。
这个最小可行性Hook就是我目前认为如何在控制多Agent并行开发过程中,迅速切换上下文来当Agent需要授权或者类似操作时,降低开发者理解以及认知负担的原型机(Prototype)
前段时间忙了好久博士的事情,以至于一直没更新,不过今天我Nick又回来啦,嘿嘿!
3 个帖子 - 3 位参与者