Agent Hooks 钩子机制
系列入口:如何使用 Agent。Hooks 是对人工把关的自动化补充,配合驾驶员心智阅读。
一、一句话理解 Hook
Hook(钩子)是你预先配置好的规则:当 Agent 运行到某个生命周期节点、满足某个条件时,自动执行一个动作。
它和普通指令的区别在于「自动」且「确定」:不是你提醒 Agent「记得格式化」(它可能忘),而是配置成「每次它改完文件,系统自动跑一遍格式化」。这把你反复做的把关,从「靠模型自觉」变成「靠机制强制执行」,更可靠也更省心。
二、三要素:触发点、条件、动作
1. 常见触发点
不同工具命名不同,但大致对应 Agent 执行流程上的几个节点:
- 执行命令前(PreToolUse / before command):命令真正运行前触发,可用来审查或拦截;
- 执行命令后(PostToolUse / after command):命令结束后触发,可对结果做处理;
- 写入/修改文件后:文件被改动后触发,适合自动格式化、lint;
- 会话停止/结束时(Stop / SessionEnd):Agent 收尾时触发,适合跑测试、生成报告。
2. 匹配条件
不是每次都执行,而是按条件命中,比如匹配命令名(rm、git push)、匹配文件路径或后缀(*.py)、匹配某个正则。条件越精准,Hook 越不会误伤正常流程。
3. 动作类型
- 阻断/拒绝:让某个命令不执行(前置 Hook 最有用),相当于自动踩刹车;
- 运行脚本/命令:跑格式化、lint、测试、类型检查;
- 注入反馈:把脚本的输出交回 Agent,影响它下一步判断;
- 记录/通知:写日志或提醒你。
三、一个最小配置实例
以「命令前拦截危险删除」和「写文件后自动格式化」为例(各工具配置文件格式略异,结构相通):
{
"hooks": [
{
"event": "before-command",
"matcher": "rm\\s+-rf\\s+/",
"action": { "type": "block", "reason": "拒绝危险的递归删除根目录命令" }
},
{
"event": "after-write",
"matcher": ".*\\.py$",
"action": { "type": "run", "command": "black ${file}" }
}
]
}第一条在危险命令执行前直接拦下;第二条每次写完 Python 文件就自动格式化。它们把你最容易忘、又最关键的检查固化成了机制。
四、典型用途
| 目标 | 触发点 | 动作 |
|---|---|---|
| 阻止危险/越界命令 | 执行命令前 | 阻断 |
| 自动格式化/lint | 写文件后 | 跑工具 |
| 提交/停止前跑测试 | 会话结束 | 运行测试,失败则反馈 |
| 强制规范 | 命令/文件命中条件 | 校验并注入结果 |
五、一个常见实战组合:提交前自动把关
来看一个很多团队都会配的场景。你希望在 Agent 准备结束一轮工作时,自动确保代码质量,而不是靠你或它「记得」去跑:
- 在写文件后触发自动格式化和 lint,让代码风格始终统一;
- 在会话停止前触发测试,测试失败就把结果注入回 Agent,让它先修,而不是带着红测试结束;
- 在执行命令前拦截
rm -rf /、未确认的git push --force等危险操作。
这一套配合起来,等于给 Agent 装了一条自动流水线:写出来的代码被格式化、被 lint、被测试,危险命令被提前踩刹车。你不再需要反复叮嘱「记得测试」,因为机制会替你执行。关键区别在于——叮嘱靠自觉、可能被遗忘,Hook 靠机制、确定生效。
六、注意事项
- 先验证 Hook 本身:一个写错的 Hook 可能阻断正常工作或反复误触发。配好后用几个真实操作试,确认命中和动作符合预期。
- 从「只读/可恢复」的动作起步:先做格式化、lint 这类低风险自动化;涉及阻断或有副作用的动作要谨慎。
- 匹配条件尽量精准:条件过宽会误伤正常流程,让每次操作都被打断,反而降低效率。
- Hook 是把关而非思考:它擅长执行确定的规则,但不能替代你对方案和复杂取舍的判断。
- 团队共享时纳入版本控制:和 Skill 一样,团队级 Hook 应随仓库走,保证所有人一致。
七、常见问题
Q:Hook 和规则文件(rules)有什么区别? 规则是每次都给 Agent 看的文本约束,靠它自觉遵守;Hook 是在生命周期节点由系统确定执行的程序动作,不依赖模型记不记得。需要硬性强制的检查更适合 Hook。
Q:Hook 能不能做复杂的语义审查? 不擅长。Hook 适合确定、可脚本化的检查;需要理解和判断的语义审查应交给独立校验 Agent,两者常配合使用。
Q:配了 Hook 导致正常命令也被拦怎么办? 通常是匹配条件写得太宽。先收窄 matcher,再用真实操作验证;涉及阻断的 Hook 一定要先在安全范围内试。
七点五、Hook 的核心价值:把「自觉」变成「机制」
Hooks 看似只是一些自动触发的小配置,但它代表了一种非常重要的工程思路转变——把原本依赖人或模型「自觉」遵守的约束,变成由系统「强制」执行的机制。 理解这层转变,你才能判断哪些事情真正值得用 Hook,而不是把它当成普通的自动化脚本。
设想你反复叮嘱 Agent「提交前记得跑测试」「别用危险的删除命令」。这些叮嘱的问题在于,它们的生效完全依赖执行者当时记不记得、愿不愿意遵守;而只要存在遗忘的可能,在足够多次的重复里,遗漏迟早会发生。模型尤其如此——它不是一个有稳定习惯的人,每次都在根据当下上下文做判断,你无法保证同一句提醒被始终如一地执行。
Hook 直接绕开了「记不记得」这个不可靠环节:你不是在请求它做某事,而是在系统层面规定,每当某个节点被触发、命中某个条件,动作就一定发生。危险命令在执行前必然被检查、文件写完后格式化必然运行,这些都不再取决于自觉。可靠性的来源从「概率性的提醒」变成了「确定性的机制」,这正是 Hook 最本质的价值。
顺着这个思路,你就能清楚判断 Hook 的适用边界:凡是「规则确定、可脚本化、且需要每次都稳定执行」的把关,都适合机制化;而那些需要理解情境、灵活权衡的判断,则无法写成确定规则,应留给人或独立校验 Agent。把这两者分清楚,你就不会用 Hook 去做它力所不及的语义审查,也不会让本该强制的关键检查继续依赖脆弱的自觉。
七点六、从个人自动化到团队护栏
单个开发者用 Hook,往往是为了让自己少盯防、少遗忘;而当 Hook 进入团队协作,它的角色会进一步升级为一道统一的「护栏」,这其中有几点值得专门说明。
个人环境里,一个 Hook 配得宽一点严一点,影响的只是你自己;但团队级 Hook 会作用于所有人的每次操作,因此配置的精准和稳健要求更高。一条写得过宽的拦截规则,可能让整个团队的正常命令反复被打断;一个有副作用却没验证好的动作,可能在多人环境里被频繁触发。所以团队 Hook 更要遵循「从可恢复动作起步、匹配条件精准、先充分验证」的原则,并通过评审再推广。
团队 Hook 最大的价值,是让质量与安全底线不再因人而异。在没有统一机制时,是否跑测试、会不会误删、格式是否规范,取决于每个人的习惯和细心程度,团队整体水平被最弱的一环拖累;而当这些关键检查被固化成随仓库走的 Hook,无论新人还是老手、无论当时是否专注,底线动作都会被一致执行。这相当于把团队里最靠谱那个人的好习惯,复制给了所有人和 Agent。
要注意避免的是把 Hook 变成束缚。护栏的意义是拦住真正危险、真正不该发生的动作,而不是把所有正常操作都加上层层摩擦,让人寸步难行。好的团队 Hook 应当「对危险动作零容忍、对正常流程零打扰」。把握好这个分寸,配合版本控制和团队共识,Hooks 就能从个人省事的小技巧,变成守护整个团队质量底线的可靠机制。
七点七、Hook 本身也需要被测试
人们很容易把 Hook 当成「配好就可靠」的东西,毕竟它的卖点就是确定性执行。但要注意:确定执行的是你写下的规则,而规则本身完全可能写错。 一个有缺陷的 Hook,其危害恰恰来自它会被「确定」地反复触发,因此在启用前,Hook 本身也需要像被测对象一样被认真测试。
Hook 常见的错误有几类。其一是匹配条件写得过宽,本意只想拦截某条危险命令,结果一批正常命令也被命中,让每次操作都被打断;其二是动作本身有问题,比如格式化命令参数写错、路径不对,导致每次触发都报错甚至破坏文件;其三是触发点选择不当,把本该在事后处理的动作挂到了事前,或反过来,使逻辑错乱。这些问题在静态阅读时未必明显,却会在真实操作中立刻暴露。
合理的做法是:配好后,先构造几个能命中的真实操作,确认它在该触发时确实触发、动作正确;再刻意做几个不该命中的正常操作,确认它不会误伤。只有「该响时响、不该响时安静」都验证过,这个 Hook 才算合格。对那些会阻断流程或产生副作用的 Hook,更要先在安全、可恢复的范围里试,避免一个写错的规则直接卡住整个团队的正常工作。
七点八、什么时候不该用 Hook
和所有工具一样,Hook 也有明确的边界,知道什么时候不该用,和知道什么时候用同样重要。最根本的一条是:Hook 只适合那些规则确定、可以明确写成触发条件与动作的事,超出这个范围,硬用 Hook 只会带来脆弱和误伤。
如果一项把关需要理解上下文、结合情境灵活判断——比如判断一段代码的设计是否合理、方案有没有满足模糊的业务意图、两处看似不同实则等价的逻辑该如何取舍——这些都无法被压缩成一个确定的匹配条件和固定动作,强行写成 Hook,要么规则简单到挡不住真正的问题,要么繁复到稍有变化就误判。这类工作应交给有独立视角的校验 Agent,或由你亲自判断。
另一个不该用的情形,是把 Hook 当作提醒人「应该更努力」的工具。Hook 能强制的是动作,不是意识;如果问题根源是团队对质量标准缺乏共识、或流程本身设计混乱,指望加几条 Hook 来解决,是开错了药方——机制只能固化已经清晰、达成共识的规则,却无法替你产生判断和共识。让 Hook 安守在「确定规则的强制执行」这一格,把需要思考的留给合适的角色,它才会可靠而不越界。
七点九、触发点选择:事前还是事后
配置一个动作时,第一个要决定的就是把它挂在事前还是事后,触发点选错,整个 Hook 的逻辑就会别扭甚至失效。理解两者的本质区别,能帮你一次选对。
事前触发的核心能力是「阻止」。 因为动作发生在命令真正执行之前,它可以审查输入、判断是否命中危险条件,并在结果不可逆之前拦下。凡是涉及「不希望这件事发生」的把关——危险删除、未确认的强制推送、越权命令——都必须放在事前,因为事后再处理,错误已经造成,拦不住了。事前 Hook 就像一道闸门,价值在于让某些动作根本不发生。
事后触发的核心能力是「处理」。 命令已执行、文件已写入,此时适合对既成结果做加工——格式化刚写好的代码、跑一遍 lint、把结果注入回 Agent 供下一步参考。它不阻止动作,而是确保动作的结果符合要求。选择时只需问一句:我是要「不让它发生」,还是要「让它发生后被处理好」?前者事前,后者事后。这个判断简单,却覆盖了绝大多数 Hook 的挂点设计。
七点十、别让 Hook 链变成「暗礁」
当你配置的 Hook 越来越多,还会出现一个进阶问题:多个 Hook 相互关联、先后触发,形成一条不那么显眼的「Hook 链」。管理不当,这条链会成为流程中的暗礁——某个你没留意的环节在悄悄阻断或改变行为。
风险在于,单个 Hook 你可能都验证过,但它们组合起来的整体效果未必被想清楚。比如一个写后 Hook 触发了格式化,格式化改动文件又触发了另一个 Hook,后者注入的反馈改变了 Agent 的下一步,这种连锁反应在孤立看每个 Hook 时并不明显,却可能造成反复触发、意外阻断,甚至在极端情况下形成循环。系统越自动,这类由组合产生的意外越难凭直觉预见。
务实的做法有几条:保持 Hook 数量精简,只机制化真正需要强制的规则;意识到一个 Hook 的动作可能触发下一个,设计时就想清楚有没有连锁或回环;给 Hook 加上必要的日志,让它触发时留有痕迹,出问题时你能看清是哪一条、以什么顺序在起作用。让自动化的链条保持简短、可见、可追溯,它才是可靠的护栏,而不是潜伏在流程里、某天突然让一切卡住的暗礁。
七点十一、给 Hook 写动作脚本的几个原则
Hook 的动作常常是一段脚本,脚本写得好不好,直接决定这个自动化是帮忙还是添乱。这里给出几个写 Hook 动作脚本的实用原则,帮你避开常见的坑。
第一,脚本要幂等,也就是同一件事被触发多次,结果都一样、不会越改越糟。因为 Hook 可能因文件被反复改动而多次触发,非幂等的脚本在重复执行下可能产生意外。第二,脚本要快速、安静,避免一个 Hook 跑很久、打印大量输出,否则每次正常操作都被拖慢、屏幕被刷满,自动化就成了干扰。第三,脚本要用退出码和精简输出表达结果,让系统和 Agent 能明确知道成功还是失败、失败原因是什么,而不是靠解析一大堆杂乱日志。
第四,也是安全相关的:脚本里不要硬编码密钥,不要在动作中做超出预期的副作用,它接收的输入(如文件路径、命令)要当作不可信数据谨慎处理,避免一条精心构造的输入借 Hook 脚本之手做出危险动作。最后,脚本本身也要纳入版本控制并可单独测试,这样它和普通代码一样可评审、可复现、可回滚。把这几条守住,你的 Hook 动作才会是一个稳定、安静、安全的自动化部件,而不是流程里新的不确定来源。
总结
Hooks 让你把「每次都要做的把关」从靠 Agent 自觉变成靠机制强制执行:在命令前后、写文件、会话结束等节点,按匹配条件自动阻断危险操作、跑格式化或测试——从低风险动作起步并验证,就能在不失控的前提下显著减少手动盯防。