DeepDojo.AI发布 2026-11-09更新 2026-11-09English

Hook 明明报错了,危险操作还是执行了

Claude CodeAI 编程

一个 PreToolUse Hook 因为依赖文件缺失,脚本以状态码 1 退出,终端里报了错。可那个受保护的操作,照样执行了。

很多人在这里会愣一下:脚本都失败了,难道不算拒绝吗?不少人以为写了“只读”、配了 Hook 就安全了,其实危险动作仍可能发生,关键在于控制有没有真的覆盖实际行为。

Claude Code 里至少有三层控制,各管一段:

  • Hook 在固定事件上执行检查
  • permissions 决定一次工具调用是否允许
  • sandbox 限制命令能碰到的文件和网络

三层里,Hook 的规则最容易被想当然,它能不能阻断动作,取决于事件、返回的决定和覆盖范围,连退出码 1 和 2 的含义都不一样。

要确认一个控制是否真的生效,办法是沿着真实的动作去问三件事:调用前检查了什么,执行时能碰到什么,失败和超时怎样表达。

三层控制各管什么,PreToolUse 怎样用结构化的拒绝来阻断,脚本失败、超时、换一种工具执行时会发生什么,以及一组可以在无副作用的环境里照着测的情境,都按这个问法展开。

配置文件里写了什么,只是起点。

01 先分清各种控制管什么

故障调查可能同时遇到长日志、共享文件冲突和有副作用的诊断命令,它们需要不同控制:独立上下文承接探索,独立工作目录分开试验改动,权限和资源限制控制实际操作。文字中的“只读”说明任务边界,但是否真正限制了写入,需要检查工具、权限和执行环境。

先把边界放在对应位置:

要控制的对象主要机制控制发生在哪里
固定事件上的动作与检查Hook工具调用前后、会话等生命周期节点
是否允许调用某类工具或操作permissions工具执行的权限判断
Bash 命令能访问哪些文件和网络sandbox命令及其子进程的执行环境

另外两种隔离手段,Subagent 管信息,worktree 管文件目录,放在多个子代理并行改代码,先划分好谁写哪份文件里讲。

小黑用事件门、权限检查、沙箱围栏和两条隔离支线控制任务进入最终验证结果

控制要对应实际行为。任务卡住却没有通知,可以检查超时与通知安排;忘跑 lint,应检查流程和适合的固定检查点;每种症状对应各自的控制,“再加一个子代理”只解决上下文问题。

02 设计 Hook 的触发与判断

Hook 在指定生命周期事件发生且匹配配置时运行。用它之前先想清楚一件事:你要预防一个动作,还是处理它已经产生的结果?输入与决定方式由具体事件约定,事件和字段以 Hooks 参考文档为准。

下面的门禁主要使用 command Hook,也就是执行脚本的处理器。先确定动作发生前还是发生后:

需求对应时机能达到什么效果
拒绝一次准备执行的危险工具操作PreToolUse在动作发生前给出阻断决定
编辑后格式化或轻量检查PostToolUse处理已经发生的改动
提供当前环境摘要SessionStart让会话获得简短初始事实
压缩前保存状态PreCompact在摘要前保留必要交接信息
主会话停止响应时通知Stop通知这一响应阶段结束,业务验收是否通过要另行确认

以 Bash 调用为例,PreToolUse 脚本从标准输入读取事件 JSON,并检查 tool_input.command。判断为越界操作时,可以以退出码 0 输出事件约定的结构化拒绝:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Deletion outside the approved task scope."
  }
}

hookSpecificOutput 中的 hookEventName 标明事件,permissionDecision: deny 表示拒绝,permissionDecisionReason 说明原因。这是处理器运行时返回的结果,检查在脚本实际运行时才发生。

匹配 rm -rf 可以演示这一过程,但删除还可能通过其他写法、脚本或工具发生。明确范围的脚本只能保证自己覆盖的检查;权限和运行环境应承担更广的资源边界。

03 检查门禁的失败处理

command Hook 要写清四项:输入字段、输出格式、退出状态和超时。这几项处理不到位时,脚本报错后动作仍可能继续执行。

对 PreToolUse 可以按事件约定用退出码 2 阻断,并在 stderr 给出原因;也可以使用上一节的结构化拒绝结果。只返回普通退出码 1、没有有效拒绝决定时,通常只是非阻断执行错误。要拒绝,就明确返回拒绝决定,这和 shell 里“非零即失败”的直觉不同。较新版本的 Claude Code 还提供 onFailure: "block" 配置,让 Hook 自身失败时也阻断动作;是否可用、默认值是什么,以当前版本的文档为准。不同事件的行为不同,PostToolUse 在动作之后运行,原动作已经完成。

开头的例子正是如此:依赖文件缺失使脚本以状态码 1 退出,这只说明检查失败;若没有有效拒绝决定,受保护操作仍可能执行。验收时既要看处理器的输出,也要确认工具动作是否实际未发生,报错只说明检查失败,阻断要看动作是否真的没发生。

可以在无真实副作用的测试环境中准备以下输入,用受保护动作的模拟对象检查行为:

输入或故障应核对的结果
明确受限的目标操作是否返回有效拒绝,工具动作是否未发生
正常且获准的操作是否保留正常权限流程,其他限制保持原样
缺失字段或无法解析输入未知状态是否有明确的处理结果,按策略执行
检查依赖失败或超时是否仍满足所需控制;脚本错误要单独核对,才知道是否已阻断
同一目的换另一种工具执行原 Hook 是否覆盖,未覆盖部分由什么机制限制

这些是控制测试情境,用模拟对象完成,真实的重要文件和生产系统留在测试之外。

为 Hook 设置适合短检查的 timeout,同时明确脚本内部如何处理无法判定的情况。如果策略要求检查异常也拒绝,应按相应事件返回有效拒绝,并用异常输入测试;超时本身是一种失败,安全阻断要另行保证。

还要区分脚本能捕获的异常,和脚本根本没机会返回结果的情况。运行中的脚本可以捕获依赖文件缺失并返回有效拒绝;但脚本未启动或已被超时终止,就没有机会再输出 deny。必须持续受限的访问,还应由实际权限或资源边界限制,并测试 Hook 不可用时动作是否仍被阻止。这样才能核对正常与异常两类路径的控制。

输出只留结果、关键错误和可操作的原因,长日志会让门禁自己制造噪声。想压缩测试命令的原始返回,应在命令封装或工具输出层处理;Hook 改写不了已经返回的结果。

适合短脚本检查需要其他处理的判断
匹配受保护路径或明确危险操作架构方案是否合理:Skill 或人工 review
编辑后运行局部 formatter完整发布流程:Skill
注入环境摘要或发送状态通知长时间排障:Skill 与独立探索
保存已有状态与日志位置整个 PR 的语义审查:Subagent 或 review Skill

Claude Code 也有调用模型的 prompt 和 agent Hook 类型,能参与语义判断;即使触发时机固定,模型的判断结果也不具有确定性。选择时区分“固定事件会触发”和“判断结果是否确定”,复杂的顾问流程放在每次都跑的门禁之外。

04 调用权限与资源限制各管一段

permissions 控制工具调用是否允许、拒绝或需要相应授权;sandbox 限制受沙箱约束的命令实际能访问什么。自动批准与询问行为取决于权限模式和配置,哪些动作会弹窗要以实际配置为准,详见权限文档。

命令名称看不出副作用。npm test 可能调用数据库清理逻辑,批准前仍需核对实际脚本和目标环境。

Bash 沙箱限制的是受其约束的命令及子进程,MCP 请求和远端写入要用各自的方式限制。沙箱文档写明沙箱只覆盖 shell 命令,文件工具、MCP 服务器和 Hook 运行在沙箱之外,外部服务仍需在接入和服务端收紧资源权限。

permissions:这次工具调用是否允许
Hook:在配置的事件上检查具体条件
sandbox:受控命令执行后能触达哪些资源

例如允许编辑普通源码,同时通过权限和相应路径检查保护 .env、密钥与生产配置;允许在沙箱中运行测试,同时限定所需网络和可写目录。Hook 可以补充检查,没有覆盖到的访问仍要靠权限和沙箱。

对于外部写入,先确定具体对象和准备执行的内容,再按实际授权范围确认。已经获准的普通步骤可以连续执行;新增影响或未决高风险分支仍应明确决定。控制放在动作前,等输出之后再说“不该改”,改动已经发生了。

05 沿着实际动作验收控制方案

假设当前授权只有“查明可能原因”,可读取脱敏日志和部署记录,不允许改生产状态。先检查“只读”是否实际成立:工具访问范围应符合任务;生产服务的凭据只具备必要读取能力。本地 Bash 诊断命令如受 sandbox 约束,还要核对允许的目录与网络;远端 MCP 的写入在本地沙箱之外,要在接入和服务端控制。

再考虑固定检查点。如果某类工具调用需要额外的确定性检查,可以配置对应 Hook,并用前面的测试情境验收。Hook 出错后的处理必须符合控制要求,别让它成为唯一且未经验证的防线。

假设日志子任务提出“可能需要重启”,当前仍应返回建议、证据和未确认影响。授权范围仍由用户决定。只有用户明确要求执行修复,并确定对象、影响和执行范围后,才进入相应操作。

规则、流程和门禁怎样分层配合,可以参考CLAUDE.md 越写越长,用三个问题把规则拆开。

验收控制方案时,沿着实际动作回答:调用前哪些条件被检查,执行时能触达什么,失败和超时如何表达,结果由谁核对。关键是让每项控制的职责清楚,并确认它确实覆盖了需要限制的动作。