← 全部开发指南

Codex Hooks,异步执行与 MCP 工具接入的权限边界

AGENTS.md 和 Skill 是写给模型看的规章,hook 则是运行时的闸机:模型准备调用工具时,先由 hook 判断是否允许。它不是提醒,而是可以直接拒绝调用的执行机制。最近 Codex hooks 支持异步执行和直接调用 MCP 工具,但权限边界也需要一起理解。

更新于 2026/9/20

AGENTS.md 和 Skill 是写给模型看的规章,hook 则是运行时的闸机:模型准备调用工具时,先由 hook 判断是否允许。它不是提醒,而是可以直接拒绝调用的执行机制。最近 Codex hooks 支持异步执行和直接调用 MCP 工具,但权限边界也需要一起理解。

Codex hooks 原文配图 1

Hook 和规则文件的差别

Skill 与 AGENTS.md 进入上下文,模型读到后通常会遵守,但没有机制保证。Hook 不进入上下文,而是挂在运行时事件上。模型请求执行 rm -rf build 时,事件 JSON 会交给脚本;脚本拒绝,工具调用就不会发生。

事件分布

启动阶段有 SessionStartSubagentStart,脚本标准输出会作为上下文注入。结束阶段是 SessionEnd,默认超时只有 1 秒、最长 3 秒,不适合执行重任务。工作阶段包括 PreToolUsePermissionRequestPostToolUseUserPromptSubmitPreCompactPostCompactStopSubagentStop

最小配置示例

Hook 可以放在 ~/.codex/hooks.json~/.codex/config.toml、仓库的 .codex/hooks.json.codex/config.toml,插件也可以携带 hook。多层配置会一起加载,同层 JSON 和 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"

脚本从标准输入取得 JSON,字段包括 session_idtranscript_pathcwdhook_event_namemodel,部分事件还带 turn_idpermission_mode。退出码为 0 且没有输出时表示放行。真正执行的 handler 类型是 commandmcp_toolpromptagent 当前会被解析后跳过。

不同事件的拦截方式

  • PreToolUse 返回 permissionDecision: "deny",或退出码 2 并写明原因,可以阻止工具调用;也可以用 updatedInput 改写调用参数。
  • PermissionRequest 返回 allow 会跳过人工确认,返回 deny 会直接拒绝;多个 hook 中只要一个拒绝,结果就是拒绝。
  • PostToolUse 发生在副作用之后,不能撤回操作,但可以替换工具结果,提醒模型继续处理。
  • StopSubagentStop 返回 decision: "block" 会让任务继续,并把理由变成新的提示;想真正停止要使用 continue: false
  • UserPromptSubmit 可在输入进入模型前拦截,PreCompactPostCompact 可控制上下文压缩。

continuestopReasonsuppressOutputPreToolUsePermissionRequest 上没有实际拦截作用,permissionDecision: "ask" 也尚未实现。

Codex hooks 原文配图 2

异步 Hook 和 MCP Hook

给 command handler 加 "async": true,输出不会阻塞当前轮,而会在下一个安全点送入。它适合静态检查等不影响当前决策的慢任务:

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

一个会话最多同时运行 8 个异步 hook,完成顺序不保证;会话结束时未完成的任务会被取消。异步 hook 不能 block、批准或改写调用,需要拦截时必须使用同步 hook,SessionEnd 始终同步执行。

MCP hook 可以直接调用已连接的 MCP server:

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

占位符从事件负载取值,独立占位符保留 JSON 类型,嵌在字符串中则变成文本。这类 hook 不会自动启动 MCP server,也不会触发审批或连锁触发其他 hook。

Codex hooks 原文配图 3

信任、沙箱与安全边界

非受管 hook 首次运行前需要人工信任,信任绑定在定义哈希上;脚本改一个字,哈希变化后就要重新审查。/hooks 可以查看来源、状态并单独禁用 hook。安装插件也不等于自动信任其 hook。

--dangerously-bypass-hook-trust 只临时生效,风险应由名字本身提醒。企业可以使用 allow_managed_hooks_only,只运行受管 hook;完全关闭则在配置中设置 [features] hooks = false

Hook 是护栏,不是完整安全边界。WebSearch 等托管工具不触发 hook,write_stdin 也不会重新触发 PreToolUse。真正的越权防护仍要依靠沙箱与权限策略。输出默认限制在约 2500 token,超出部分会落到临时文件,脚本不要把密钥写入 stdout。

Codex hooks 原文配图 4

结论

Hooks 的价值在于把高频、明确的动作变成运行时约束:例如禁止改生产配置、提交前必须过 lint、触碰 migrations 目录先请求确认。日常使用可以先写一个 PreToolUse hook 管住 Bash,再逐步加入异步检查和 MCP 扫描。它能减少依赖模型自觉,但不能替代沙箱、权限和人工判断。

参考来源

AIGoCode · 面向开发者的实践与指南
Codex Hooks,异步执行与 MCP 工具接入的权限边界