用 Codex 干活的人多少都有这感觉。AGENTS.md 里写死了提交前必须跑测试,Skill 里又强调一遍别碰 migrations 目录,结果它该跑偏还是跑偏。

问题不在你写得不够狠。AGENTS.md 也好、Skill 也好,本质都是贴在墙上的规章制度,看没看见、听不听,最后是模型自己说了算。

hooks 是另一层东西,它是门口那道闸机。模型要调工具,先过闸机;闸机说不行,这次调用就真的不行。不是劝退,是拒绝。

最近OpenAI 把这道闸机改动得挺大。一句话概括:hook 能异步跑、能直接调 MCP 工具,把这套真正落进了会话里,顺手还把权限那一摊子收紧了一轮。

闸机和规章制度差在哪

差在执行时机。

Skill 和 AGENTS.md 是喂给模型的文本,进的是它的上下文。模型读了,大概率会照做,但没有任何机制保证它照做。

上下文一长、任务一复杂,忘掉是常事。

hook 不进上下文,它挂在 Codex 运行时的事件上,模型说要跑 rm -rf build,这个调用会先停下来,把一段 JSON 从标准输入喂给你的脚本,脚本退出码是 2 或者返回一个 deny,这条命令就执行不了,模型收到的是被拒绝的结果。

说白了,一个是讲道理,一个是物理隔离。

十一个事件,挂在三个时间点上

开局两个:SessionStart 和 SubagentStart。会话或者子代理刚起来的时候触发,这两个事件的脚本往标准输出打什么,什么就变成上下文喂给模型,想给它注入当前分支、待办、环境信息,用这个。

收尾一个:SessionEnd,注意它不给子代理跑,超时也特别短,默认 1 秒、最长 3 秒,只够收个尾,别在里面干活。

剩下八个都在干活过程中:PreToolUse、PermissionRequest、PostToolUse、UserPromptSubmit、PreCompact、PostCompact、Stop、SubagentStop。

名字基本自解释,真正要拿捏的是每个事件用什么姿势才能拦住。

一个能跑的 hook 长什么样

配置放这四个地方之一:/.codex/hooks.json、/.codex/config.toml、仓库里的 .codex/hooks.json 或者 .codex/config.toml,插件也能自带 hook。

这里有个容易踩的点,这几层不是覆盖关系,是全都加载。

你以为项目里的配置会盖掉全局的,实际是两边一起跑,同一层里 json 和 toml 混着写会合并,启动时还给你个警告。

写在 config.toml 里长这样:

[[hooks.PreToolUse]] matcher = “^Bash$” [[hooks.PreToolUse.hooks]] type = “command” command = ‘/usr/bin/python3 “$(git rev-parse –show-toplevel)/.codex/hooks/pre_tool_use_policy.py”‘ timeout = 30 statusMessage = “Checking Bash command”

matcher 是正则,匹配工具名。写星号或者干脆不写就是全都匹配。

脚本这边拿到的是标准输入里的一个 JSON 对象,字段有 session_id、transcript_path、cwd、hook_event_name、model,部分事件还带 turn_id 和 permission_mode,退出码 0 且没输出就是放行。

handler 支持的类型里只有两种真的会执行:command 和 mcp_tool。prompt 和 agent 能被解析,但目前解析完直接跳过,写了白写。

想真拦住,每个事件的姿势不一样

这块是最值得记的部分,因为写错了不报错,只是拦不住。

PreToolUse 拦得最干净。 返回 permissionDecision: “deny”,或者退出码 2 加一段 stderr 说明理由,这个工具调用就不会发生,它还能改写调用,返回 updatedInput 配上 allow,模型想跑的命令会被换成你给的版本。

PermissionRequest 决定弹不弹审批框。 返回 allow 直接跳过人工确认,返回 deny 直接否掉,什么都不返回就走正常审批流程,多个 hook 有分歧的时候,只要有一个说 deny 就是 deny。

PostToolUse 是事后的。 副作用已经发生了,撤不回来,但它能把工具的返回结果替换成反馈内容,相当于告诉模型这步干得不对,让它自己收拾。

Stop 和 SubagentStop 反直觉,得单独记。在这两个事件上返回 decision: “block”,效果不是拒绝,是让它接着干,你给的理由会变成新的续写提示词,想真停下来得用 continue: false。

UserPromptSubmit 能在输入进模型之前把它拦下来,PreCompact 和 PostCompact 能卡住上下文压缩。

有几个字段是写了也没用的。PreToolUse 和 PermissionRequest 上的 continue、stopReason、suppressOutput 属于这类,写了会被记成执行失败,但调用照跑不误。permissionDecision: “ask” 目前也还没实现。

异步和 MCP 工具

先说异步,给 command 类型的 handler 加一个 “async”: true:

{ “type”:”command”, “command”:”python3 ~/.codex/hooks/post_tool_use.py”, “async”:true, “timeout”:120 }

它的输出不会堵住当前这一轮,会等到下一个安全点再送进去,。适合那些跑得慢又不影响决策的活儿,比如把改动过的文件丢去跑一轮静态检查。

代价也说清楚了,一个会话最多同时跑 8 个,完成顺序不保证,会话结束时没跑完的直接取消、输出丢掉。

最关键的一条,异步 hook 拦不住任何东西,它没法 block、没法批准、没法改写,要拦就得同步,SessionEnd 无论你写不写 async,都是同步跑。

再说 MCP 工具 hook,以前 hook 只能起进程跑脚本,现在能直接调 MCP 工具:

{ “type”:”mcp_tool”, “server”:”scanner”, “tool”:”scan_patch”, “input”:{“patch”:”${tool_input.command}”}, “timeout”:30, “statusMessage”:”Scanning edited files” }

${tool_input.command} 这种占位符会从事件负载里取值,单独放着的占位符保留原本的 JSON 类型,嵌在字符串里的就渲染成文本。

这类 hook 只复用已经连着的 MCP server,不会替你启一个。它同步执行,不会触发审批,也不会连锁触发别的 hook,SessionEnd 不收这种。

改一行脚本,就得重新授权一次

非受管的 hook 第一次跑之前你得先看一眼、点个信任,而信任是绑在定义的哈希上的,脚本改了一个字,哈希变了,这个 hook 就重新变成待审查状态,在重新信任之前直接跳过不跑。

/hooks 能看当前都加载了哪些、从哪来的、哪些是新的或者改过的,也能单独禁掉某一个。

顺带一提,装了插件不等于自动信任它的 hook,这两件事是分开的。

想图省事有 –dangerously-bypass-hook-trust,名字已经把风险写脸上了,一次性生效,不落盘。

企业那边有 allow_managed_hooks_only,开了之后只跑受管的 hook,用户自己加的一律不认。整套不想要的话,config.toml 里 [features] hooks = false 关掉。

这不是安全边界

工具 hook 应该被当成一道有用的护栏,而不是一条完整的强制边界,这是官方原话的意思,具体漏在哪,也一并列了。

托管工具不触发 hook,WebSearch 就是其中一个,shell、unified exec、apply_patch、MCP 工具和本地函数工具这些会触发,但 write_stdin 不会重新触发 PreToolUse。

所以指望用 hook 把 Codex 关成铁桶,思路一开始就错了。

它的正确定位是把那些高频、明确、自己知道边界在哪的动作管住,别动生产配置、提交前必须过 lint、碰到 migrations 目录先问一句,真要防越权,还得靠沙箱和权限那一层。

这轮权限更新跟 hooks 是配套的,沙箱在 Linux 和 Windows 上对被拒绝或者读不了的路径改成了默认拒绝,被拒路径底下的操作需要重新申请审批,apply_patch 不能再借机扩大写权限,连加载 AGENTS.md 都要走文件系统权限检查了。

还有一个升级要注意,untrusted 这个审批策略已经废弃,配置里还写着它的,改成 on-request。

输出别写太多,也别打密钥

hook 送给模型看的内容默认卡在 2500 token 左右。超了会把全文写到临时目录的文件里,模型只看到提取出来的一小段预览加一个路径。

想放开就调 additionalContextLimit,填 0 就是全放行,但真别这么干,一个话多的 hook 能把整个上下文窗口吃光。

因为输出可能落盘,脚本里别往 stdout 打密钥。

写在最后

hooks 本身不是这次才有的。8 月这两版的意义在于把它从能跑个脚本,升级成了能异步、能接 MCP 工具链、能在会话恢复和分叉之后继续正常工作的一套完整机制。

具体到人,天天用 Codex 改业务代码的,先挂一个 PreToolUse 管住 Bash 就够了,把你最怕它动的那几条命令拦下来,二十行 Python 的事。

给团队搭工作流的,重点看信任机制和 allow_managed_hooks_only。hook 是能执行任意命令的,仓库里带一个恶意 hook 进来是真的风险,哈希绑定这个设计就是冲这来的。

两边都用的,Codex 的插件 hook 环境变量里同时给了 PLUGIN_ROOT 和 CLAUDE_PLUGIN_ROOT,兼容的意图很明显,从 Claude Code 那边搬 hook 过来成本不算高,最后还是那句,别把它当安全边界,官方自己都没这么说。

更多阅读:Codex Hooks 官方文档:

https://developers.openai.com/codex/hooksCodex

更新日志:

https://developers.openai.com/codex/changelog

最近发现一个好用的 AI 生图工具,分享一下。

写文章、做 PPT、搞 README 配图的时候经常需要快速出一张图,HiAPI.ai 直接输入描述就能出图,也支持生视频,响应很快,出图质量也不错。

新注册用户送 50 张 GPT Image 2 免费额度,不用绑卡,需要快速出图的可以试试。

👉

HiAPI.ai

(直达链接:https://www.hiapi.ai/invite/QwfK)