第 32 讲:Hooks 钩子引擎——发现、调度与输出协议

Codex 源码解析 · 第 32 讲

Codex 源码解析系列 第 32 讲:Hooks 钩子引擎——发现、调度与输出协议 基于 OpenAI Codex 源码 · 2026-10-26 💡 本讲一句话: Codex 把 Claude Code 风格的 hooks 做成了一个带信任分级、并发调度和严格输出协议的子系统——谁能在什么时机拦截工具调用、改写输入、阻止会话结束,全部由这套引擎裁决。读完你能看懂一个 hook 从"配置声明"到"影响模型行为"的完整生命周期。 一、Hooks 是什么:12 个事件点的拦截网 Codex 的 hooks 子系统位于 codex-rs/hooks/ (约 15.7K LOC),核心思想来自 Claude Code:在 agent 生命周期的固定事件点上,允许用户/企业挂接外部命令或 MCP 工具。协议层定义了全部 12 个事件名: 📄 codex-rs/protocol/src/protocol.rs (第 1584-1597 行) pub enum HookEventName { // 全部 hook 事件名(serde snake_case 序列化) PreToolUse, // 工具调用前:可拦截、可改写输入、可注入上下文 PermissionRequest, // 权限请求时:可代答 allow/deny PostToolUse, // 工具执行后:可追加反馈/上下文 PreCompact, // 压缩前:无状态通知类 PostCompact, // 压缩后:无状态通知类 SessionStart, // 会话开始:注入初始上下文 SessionEnd, // 会话结束:清理/上报 UserPromptSubmit, // 用户提交 prompt 时:可拦截改写 SubagentStart, // 子代理启动(matcher=代理类型) SubagentStop, // 子代理停止(matcher=代理类型) Stop, // 回合结束前:可阻止"收工",逼模型继续干活 Interrupt, // 用户中断时:通知类 } 注意事件分两类: 带对象的 (PreToolUse/PostToolUse/SubagentStart…,matcher 匹配工具名或代理类型)和 不带对象的 (Stop/UserPromptSubmit/Interrupt,配置了就触发)。这个区分贯穿整个引擎的 matcher 逻辑。 一个 hook handler 的来源可以是:内置、系统层 config.toml、用户层、项目层、MDM/企业托管、插件。来源不同, 信任等级 和 能否产生控制效果 (拦截/改写)完全不同——这是本讲的核心主线。 二、发现阶段:多层配置收敛 + 信任分级 discover_handlers() 是入口(discovery.rs,全文件 1741 行)。它把"配置层栈 + 插件来源"两类输入收敛成统一的 handler 列表: 📄 codex-rs/hooks/src/engine/discovery.rs (第 94-116 行) pub(crate) fn discover_handlers( // 发现入口:把"配置层 + 插件"两类来源统一收敛成 handler 列表 config_layer_stack: Option<&ConfigLayerStack>, // 配置层栈(系统/用户/项目/MDM…),None=只有插件来源 plugin_hook_sources: Vec<PluginHookSource>, // 插件贡献的 hook 源,先于本函数加载好 plugin_hook_load_warnings: Vec<String>, // 插件加载阶段已产生的警告,原样透传进结果 bypass_hook_trust: bool, // 调试/受信场景下跳过 hash 信任校验 ) -> DiscoveryResult { // 返回 handlers + 展示条目 + 警告 + 必需 hook 的加载错误 let mut handlers = Vec::new(); // 最终生效的 handler 列表(按发现顺序) let mut hook_entries = Vec::new(); // TUI/事件流…

阅读全文:Codex 源码解析 第 32 讲