SuperAPI AI 工具集

guardrail-hooks

编程与开发

查看项目

更新于 10-30 07:48

技能介绍

Guardrail hooks 讨论的是一条核心经验:如果一条规则能否被遵守取决于代理在关键时刻是否“想起来”,那它就应该被写成一条代码路径,因为散文化的提示文字在与代理读到的其他内容的竞争中往往落败。该技能把这一原则落成具体做法:该用哪种代码路径、怎么写一个既能守住规则又不会变成噪音的钩子,以及两个可以直接改造的钩子。

起点是一则真实记录:2026-10-12,所有者就一个措辞选择回答“你看着办”,代理把它延伸成“我的决定:删掉线上帖子明天再发”。虽然最终没有删除,但阻挡删除的只有代理的临场判断,而一条删帖命令其实只有一行。指令文件自 2026-06-02 起就写明“删除任何公开内容都需要所有者确认”,这说明文字规则挡不住执行动作,修复方式是加一个必须由所有者点击确认的钩子。

选最小的代码路径

技能给出按规则类型选择承载方式的对照原则:

  1. 对可通过前缀识别的命令,用设置文件里的权限规则(permissions.ask 或 permissions.deny),无需维护脚本;这类规则会检查每个子命令,包括 $() 和循环体内的命令,在自动模式下仍会弹窗。
  2. 当命令文本本身无法判断其行为(heredoc 中的代码、它运行的脚本、某个参数的值)时,用 PreToolUse 钩子,例如 scripts/guard_one_way_doors.py、scripts/block_wrong_identity.py。
  3. 约束自家工具的使用方式,用必填参数或工具内部的拒绝逻辑。技能记录了一个搜索分析工具在一次会话中出现四次数据错误后,改为分页拉取并写出精确时间窗口的清单,缺少清单时报告拒绝运行。
  4. 约束提交内容,用仓库内的 git pre-commit 钩子,通过 git config core.hooksPath .githooks 按克隆启用;案例是拒绝把记账文件清空或减半的提交。
  5. 约束“完成”前必须成立的条件,用返回 decision: "block" 并附原因的 Stop 钩子。
  6. 约束代理启动时应知道的信息,用返回 additionalContext 的 SessionStart 钩子,例如用代码检查 git 状态。
  7. 约束派生文件必须跟随源文件,用 PostToolUse 钩子执行同步,并且始终以 0 退出,避免同步抖动阻塞工具。

总体偏好是越靠前越好:权限规则优于钩子,工具内拒绝优于两者,因为它对所有代理和所有人都生效。当指令文件写明这类规则时,也应说明它由什么承载、在哪里不覆盖。

写得住又不吵

每条规则都应来自真实记录。技能给出的经验包括:只对所有者真正愿意点击的操作弹窗;最初的单向门守卫还拦截进程终止、rm -rf、HTTP DELETE 和破坏性 git,并扫描全部命令文本,结果在一个基准会话上因 heredoc 中含 scoped pkill -f 而两次误报,当天被收窄为只拦公开删除与发布。守卫问得太频繁会训练出条件反射式的“是”,比没有守卫更糟。

匹配实际执行的内容而非被提及的内容:匹配命令规则前先剪掉 heredoc 正文;在代码里匹配 API 调用(如 .delete_post()),而不是文档字符串可能引用的 CLI 字符串。身份守卫就曾因匹配任何位置出现的禁用地址,误拦了 git log --author=<地址> 和 JSON 中含该地址的测试;现在的脚本按命令段匹配,且只在 commit 的 --author 上匹配。

要看进命令运行的内容:命令文本守卫对 python cleanup.py 是盲的。守卫会打开命令运行的本地脚本(以及喂给解释器的 heredoc 正文),用代码规则扫描它们。

要刻意选择裁决方式:ask 会把人放进循环、在自动模式下仍强制弹窗,在无人应答的非交互运行中则拒绝;deny 拒绝并告知代理原因;退出码 2 是 Claude Code、Codex 和 Cursor 都认可的统一拒绝形式。拒绝原因也要对代理说话,因为代理会读它。

单向门的放行必须在对话中键入,不能写在侧边页面。所有者写在决策页、文档或工单里的批准,到达代理时是工具结果,而 harness 的检查不读工具结果。Claude Code 自动模式分类器只看到用户消息和代理的工具调用,工具结果被剥离,因此消息中的批准只有在点明动作及其具体对象时才清除拦截,仅说动词不够。由 ask 规则或钩子强制的弹窗无论如何都需要点击。

不能堵住出口:花费保险丝应豁免其自身的管理命令,否则超预算时无法解除。堵住清除自身命令的守卫只能手工移除。

要决定失败时的方向:默认可启动失败、崩溃、退出 1 或超时的钩子会放行动作;策略钩子应设 onFailure: "block"(Claude Code 2.1.295 起),用退出码 2(绝不用 1)拦截,并在首次运行时留意钩子错误提示。stdout 只保留一个 JSON 对象,否则决策不可读,而不可读的决策是非阻塞错误。

接线前要像程序一样测试:命令钩子从 stdin 读 JSON、以退出码和 stdout 作答,可管道输入样本载荷。scripts/test_hooks.py 就做这件事,替换规则时替换其中用例。同时要知道钩子看不到什么:PreToolUse 只对工具调用触发,不覆盖用户 @ 提及的文件,Bash 匹配器不覆盖做同样事情的 MCP 工具或浏览器;钩子是护栏,不是沙箱。要停止运行用 Stop 钩子的 continue: false,它会优先于事件级的 continue 决策;阻塞的 Stop 钩子必须检查 stop_hook_active。

脚本与安装

所有脚本是标准库 Python,在文件顶部 CONFIG 块(或 JSON 文件、环境变量)中配置,从 tool_input.command(Claude Code、Codex)或顶层 command(Cursor 的 beforeShellExecution)读取命令,显式按 UTF-8 读写以避免 Windows 代码页问题。

scripts/guard_one_way_doors.py:在不可逆或公开操作前询问、拒绝或退出 2;命令规则作用于 shell 实际运行的内容并剪掉 heredoc,代码规则作用于 heredoc 正文和命令运行的本地脚本;可配置 command_rules、code_rules、skip_scripts、mode,或用 --config <file> / ONE_WAY_DOORS_CONFIG,及 ONE_WAY_DOORS_MODE=ask|deny|block。

scripts/block_wrong_identity.py:当禁用地址被设为 git 身份(user.email、-c user.email=、GIT_AUTHOR_EMAIL= 等)或作为提交的 --author 传入时退出 2,搜索该地址则允许;可配置 blocked_emails 或 BLOCKED_GIT_EMAILS,及 GIT_IDENTITY_HINT。

scripts/test_hooks.py:把样本载荷管道输入两个脚本,检查退出码和决策,覆盖触发项、相似非触发项、各模式、配置文件、Git Bash 路径和非 ASCII 规则。

在 Claude Code 中安装:把脚本复制到 .claude/hooks/(单项目)或 ~/.claude/hooks/(本机所有项目,云会话不读);替换示例规则后运行 python <skill dir>/scripts/test_hooks.py 直到通过;在旁边的设置文件中接线,使用 exec 形式(args)把路径作为单个参数传入。在 ~/.claude/settings.json 中没有项目目录,应给绝对路径并用正斜杠;python 为 Python 2 或缺失时用 python3。随后打开 /hooks 确认两个钩子已列出,再让代理运行 echo gh repo delete test/test,守卫应匹配并弹窗,然后拒绝。

Codex 在 .codex/hooks.json 中接线并把 mode 设为 block,因为 Codex 会解析 ask 但尚不支持,会标记运行失败并放行;每次编辑后都要在 /hooks 中重新信任钩子。Cursor 在 beforeShellExecution 上以 block 模式注册,或把输出适配为其 {"permission": ...} 形状,详见 references/hook-contract.md。

Windows 注意事项:shell 形式的钩子经 Git Bash 运行,正斜杠或 exec 形式可避免反斜杠被吞;tool_input 中的文件路径带反斜杠,匹配 /src/ 这类段前要归一化;无 Git Bash 时在 PowerShell 中运行,应使用较新版本并设置 onFailure;Python 钩子输出默认用 ANSI 代码页,脚本已重配 stdout/stderr 为 UTF-8 并以 UTF-8 解码 stdin。

复核

技能中的厂商事实都标注日期,钩子每月都可能变化:应定期用 claude --version 核对并重读 hooks 参考(https://code.claude.com/docs/en/hooks.md,加 .md 取原始页),确认 Codex 是否已支持 ask、Cursor 哪些事件强制 ask 及其输入形状;运行 scripts/test_hooks.py,然后在真实会话中触发每个已接线的钩子一次。测试只能证明脚本,只有真实触发才能证明接线。成功标志是:样本中必须触发的被询问或拦截、相似项保持沉默,且真实会话中调试日志显示钩子运行过。

文件方面:references/hook-contract.md 记录 Claude Code 的钩子契约与 Codex、Cursor 的差异(均标注日期),编写或调试任何钩子时阅读;两个模板脚本分别在门禁不可逆/公开操作和代理遇到不能用于提交的地址时使用;测试脚本在每次修改钩子或其规则后运行。