Claude Code 更新强化钩子失败处理
文章梳理 Claude Code 2.1.317 中与安全配置有关的变化,包括 SDK 钩子回调阻断及邮件泄露相关确认。重点解释命令钩子的退出码语义,以及部分钩子报错或超时后仍继续执行的行为。
AI 深度解读
Claude Code v2.1.317 在 11 月 3 日发布,距离 2.1.316 只有三天。这次值得关注的,是几类容易被当成成功的失败:守卫脚本崩了,工具照跑;磁盘满了,命令仍返回成功;配置写进文件,却没进入当前会话。
作者梳理更新日志和 hooks 文档后提出一个判断:Agent 的可靠性,取决于出错时怎样收场。对用 Claude Code 接入 Node 工具、CI 或云端任务的程序员,这比多一个功能更值得检查。
守卫脚本出错,为什么操作还能继续
Claude Code 的命令 hook 用退出码表达结果:exit 0 表示成功,exit 2 表示阻止操作,其他退出码属于非阻断错误,操作仍会执行。
这里最容易踩坑的是 exit 1。普通脚本常用它表示失败,但在策略 hook 中,它不能表达「禁止执行」。如果 PreToolUse 脚本路径写错、启动失败或超时,多数事件默认仍然放行,只在会话记录里留一条提示。这种策略叫 fail-open,即检查机制失效时继续执行。
写了检查规则,不等于检查失效后还能拦住操作。
从 v2.1.295 开始,settings.json 里的 command 和 http hooks 已经可以通过 "onFailure": "block",把 hook 故障转为该事件上类似 exit 2 的阻断结果。v2.1.317 新增的是 Agent SDK hook callbacks 对同一选项的支持,覆盖把 Claude Code 嵌入自有工具时注册的进程内回调。
下面这份配置解决的是 Bash 守卫自身失败后仍然放行的问题,展示的是已有的 command hook 写法:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node",
"args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js"],
"onFailure": "block"
}
]
}
]
}
}
要看的是 "onFailure": "block" 这一行,不能把整个示例误认成 SDK 回调的注册格式。原文没有给出 SDK 的具体代码。
这里的故障也不只包括崩溃:无法启动、退出码不是 0 或 2、HTTP 错误、超时、输出非法 JSON,都在覆盖范围内。但这个开关对 Stop、SubagentStop、TaskCompleted、TeammateIdle 和 async hooks 无效。
文档给出的验证办法很直接:临时删除守卫脚本,请求执行一个无害命令,预期应收到阻断提示。它检查的是守卫失效后的行为,正常运行一次无法替代这个测试。
邮箱外传和额外连接器,开始有了提示
v2.1.317 增加了一项检查:web 或 shell 工具调用准备把账户邮箱发送到网络时,会先请求用户同意。
作者用提示注入解释它的价值:README 或测试文件里的恶意指令,可能诱导 Agent 用 curl 向某个 webhook 上传环境信息。账户邮箱是能跨组织关联身份的稳定标识,值得单独设一道确认。
不过,原文没有给出识别范围、编码绕过情况或误报测试。能确认的范围是「账户邮箱外传前询问」,不能把它扩展理解为所有敏感数据都有了防泄漏保障。
另一处变化发生在 /schedule。云端定时任务如果附带了用户未请求的 connectors,新版本会显示警告,并说明如何移除。
这两项改动针对不同问题:一个让具体的数据外传动作进入确认流程,一个把任务额外获得的连接器暴露出来。原文只说后者会警告,没有说会自动删除连接器或阻止任务运行。
作者还把这些变化与 10 月 30 日讨论的 marketplace 恶意软件问题、10 月 26 日的 MCP tasks 变化联系起来。它们构成作者的观察线索,并不是这次版本安全效果的对照证据。
CI 最怕的,是文件没写成却返回成功
更新前,磁盘满或配额耗尽时,claude -p 可能在丢失 transcript,或写入 ~/.claude.json 失败的情况下,仍然返回 exit 0。流水线看到的是成功,会话记录或配置却没有保存下来。
v2.1.317 对这些情况输出错误,并在 text 和 json 输出模式下返回 exit 1。这里的范围要保留:原文明确提到这两种输出模式,没有说明其他模式。
对用 claude -p 驱动检查、合并或部署流程的团队,这修正了一个错误的成功信号。作者建议同时检查退出码和 stderr,因为非零退出码只能告诉你失败,具体原因还得看错误输出。
配置加载也有一个修复:启动后最初约一秒内修改 settings 文件,过去可能要等文件再次变化才生效。文件已经落盘,当前会话却没用上新规则。会在启动时自动注入 hooks 或权限配置的团队,需要重新验证实际加载结果。
管理侧还增加了两类信息。OpenTelemetry 的 metrics、events 和 spans 新增 anthropic.plan、anthropic.seat_type、anthropic.provider、anthropic.custom_base_url,帮助按套餐、席位和路由来源区分行为与花费。网关管理 API 新增 GET /v1/organizations/analytics/cost,可按日期范围查询历史成本,支持总额、开发者、每日或 IdP group 维度。
另一个行为变化关系到成本与执行强度:subagents、teammates 使用与主会话不同的模型时,现在采用该模型保存的 effort level 或默认值。此前依赖会话 effort 统一控制子任务的配置,需要重新核对。
我的判断
作者把注意力放在失败路径上,这个方向是对的。策略检查遇到故障时放行,确实可能让一份看起来完整的配置失去约束力。但它也带着取舍:启用故障阻断后,脚本路径错误或服务超时都会影响正常工作,需要有人维护和排查。
适合安全守卫的失败策略,未必适合日志、通知等辅助 hook。
作者最后把「所有策略 hook 启用阻断」「拒绝时用 exit 2 并向 stderr 写原因」「CI 检查退出码」加入检查清单。这是基于文档语义提出的配置建议。原文没有展示完整实测记录,也没有提供升级前后的事故率、误拦率或性能对照,不能据此宣称新版已经全面可靠。
hook 也不该承担全部权限控制。相比只靠一段脚本拦截,执行环境本身的权限边界仍然有价值。这是站内判断,原文没有比较两种做法的效果。
能拿来做什么
- 检查项目里的
PreToolUse守卫,区分「规则拒绝」和「脚本故障」:前者用exit 2并输出原因,后者在适用事件中配置onFailure: "block",再用临时移走脚本的方式验证。 - 如果通过 Agent SDK 封装内部 Node 工具,核对所用 SDK 对回调失败阻断的支持和注册方式;原文的 command hook 示例不能直接当作 SDK 接口代码使用。
- 在调用
claude -p的 CI 中检查退出码、错误输出以及流程依赖的 transcript 是否存在,并确认 text 或 json 模式下的写入失败会使任务失败。 - 使用云端任务或启动时自动改配置的团队,分别检查
/schedule的额外连接器警告,以及新会话实际加载的规则;使用不同模型执行子任务时,同时核对 effort 设置。