你在服务器上跑智能体,多半给它配过钩子——命令执行之前先过一道自己写的脚本,危险动作一律拦下来。这道防线你写了,也测过,看着挺稳。可它一直有个不太起眼的毛病:钩子自己坏掉的时候,动作照样放行。脚本被删了、等超时了、或者中途崩了,拦截就这么悄悄消失,而且没有任何人被告知。平时看不出来,等到某天回头复盘,才发现那道检查早就没在执行了。更麻烦的是,这类问题不会自己暴露,你不主动去查,它能一直静静地待在那里,直到某次真正危险的命令从缺口里溜过去。
Claude Code在2026年10月8日的2.1.295版本里,把这条默认值改了口径。新增的onFailure: block参数,让命令钩子和HTTP钩子可以设置成失败即拦截——钩子起不来、超时、或者返回了非预期的退出码,动作会被直接挡下,而不是放行。这个开关是可选的,装不装由你决定,已经写好的老配置保持原样,不会被动改掉。换句话说,工具这次递过来的是一把锁,要不要上锁、锁哪几道门,最后还是使用者自己拍板的事,默认值只提供了一种更安全的选择。
官方的口径把失败拆成了三种。第一种是根本跑不起来——脚本被删了、没有执行权限,或者解释器在那台机器上压根没装。第二种是超时——钩子背后挂的是内部策略服务,服务一慢或者一挂,钩子就等不到结果。第三种是退出码不对——脚本自己崩了,抛出来一个异常,而不是给出明确的判断。这三种过去都是静默放行的。静默是这个缺口的要害,它既不报错也不提示,只是把本该被拦下的动作悄悄放过去,你以为通过了检查,其实是检查根本没运行。
这三种在现实里都不是边缘情况,而是坏日子里必然发生的事。一次部署顺手移除了个依赖,策略接口被流量压垮,或者Python升了个小版本把某个import弄崩。在放行的世界里,你的护栏坏掉的那一天,就是你完全没有护栏的那一天,而且没人来通知你。真正麻烦的地方在于,它会让你误以为防护还在。安全里最昂贵的代价,从来不是某道防线坏了,而是它坏了却没人知道,于是在错误的安心感里继续往前走,直到撞上那件本可以被拦下的事。
要说清楚的是,这个开关补的是钩子层的短板,不是取代下层的权限和沙箱。真正必须绝不发生的动作,得落在确定性的命令钩子上,再叠上更底层的权限与沙箱。让模型自己判断的那类钩子不在覆盖范围内——它是概率性控制,本来就不该拿来守底线,只适合做辅助判断。把这层关系分清楚,你就不会指望一个概率性的判断去守必须零失误的底线,该硬的地方用确定性手段,该软的地方再交给模型。
官方特别提了一句,别一刀切全开。挡住密钥读取、生产部署、破坏性命令、合规检查这类确定性闸门,适合失败即拦截;而日志、格式化、通知这类辅助钩子,保持失败照常更合理——策略服务偶发抖动,不该把整条开发流程卡死。哪种该硬、哪种该软,取决于这道钩子拦错了会不会出人命。判断的标准其实很简单:拦截失败和放行失败,哪一种后果更不可接受。前者顶多麻烦一点,后者可能直接出事,账一算就清楚了。
失效即放行,是钩子最大的漏洞
把这句话摊开讲:一道只在自己能正常工作时才生效的防线,本质上是把安全的开关交给了运行环境。钩子跑得起来,你就有防护;钩子跑不起来,你什么都没有,却还以为自己有。这种错觉比没有防护更危险,因为它会让人放松警惕,把本该双保险的动作只交给这一道。这也是为什么,越是无人值守的场景,越要把默认值往严的一边调。人不在场的时候,工具必须替你把最坏的情况先假设一遍。
这个缺口在无人值守的场景里最要命。CI流水线、定时任务、夜里自动跑的智能体,这些地方没人盯着屏幕。钩子静默失效的那一刻不会弹任何提示,等真出了事回头翻日志,才发现那道检查已经好几天没真正执行过了。有人值守时还能靠肉眼补位,无人值守时只能靠钩子自己不装死。有人值守的时候还能靠人补位,无人值守就只能靠机制本身不出岔子,而机制不出岔子的前提,是它坏掉的时候会明确地告诉你。
迁移的成本不高。翻一遍自己的钩子配置,把执行规则的和只是报告的分开。前者加上onFailure: block,再配一个可靠的超时,让慢吞吞的策略服务变成一次干脆的拦截,而不是长时间的挂起;后者原样不动就行。这道工序值得写进团队的上线检查清单,而不是等出事之后再补。这十分钟花在事前,能省下的是事后几个小时的排查,以及一次面对上级或客户时说不清楚的交代,怎么算都划算。
钩子这东西的价值,从来不在于它平时拦下了多少,而在于你最需要它的那一刻它还在不在。把失败即拦截打开,等于承认一个更朴素的事实:一个会自己坏掉的守卫,本来就不该被当成守卫。默认值改过来只是第一步,真正让它生效的,是你愿意花十分钟去把每个钩子归类。
申请创业报道,分享创业好点子。点击此处,共同探讨创业新机遇!
