首页
/ Open Interpreter 钩子(Hooks)机制完全指南:在代理生命周期关键节点执行受信任的确定性脚本

Open Interpreter 钩子(Hooks)机制完全指南:在代理生命周期关键节点执行受信任的确定性脚本

2026-09-06 19:24:58作者:裴麒琰

导读

Open Interpreter 的钩子(Hooks)机制允许你在代理(Agent)运行生命周期的特定时点,以确定性的方式触发外部命令或脚本——例如在模型调用 shell / 补丁 / MCP 工具之前做策略检查、在会话启动时注入自定义上下文、对用户提示做扫描与记录、或在回合结束后做运行验证。本文以 docs/zh/hooks.md 为主体骨架,结合仓库中 hooks 模块 的 Rust 实现与 配置模块 的源码细节,完整覆盖钩子的启用方式、存放位置与信任模型、全部生命周期事件、JSON/TOML 两种配置形态、匹配器规则,以及命令钩子与宿主之间的输入输出协议。读完本文,你将能够独立编写、托管并安全运维自己的钩子脚本,把策略与治理能力精确嵌入 Open Interpreter 的每一步动作。


钩子机制概述与适用场景

钩子让你能够在 Open Interpreter 事件的前后运行确定性的命令。文档明确列举了四类典型诉求:

  • 策略检查(policy checks):在危险命令真正执行前拦下它;
  • 日志记录(logging):把模型每次调用的工具、参数与结果落盘留痕;
  • 提示扫描(prompt scanning):在用户提示进入模型之前做敏感信息过滤;
  • 自定义上下文注入(custom context injection):把仓库状态、环境信息等注入到模型可见的上下文里;
  • 运行后验证(post-run validation):工具执行完成后做结果校验。

关键设计定位:钩子是可选的“生命周期脚本”,是安全护栏(guardrails),而不是沙箱(sandboxing)与批准(approvals)机制的替代品。若你期望强隔离与强授权,仍需依赖 Open Interpreter 自身的沙箱与权限批准体系,二者应组合使用。

默认启用与特性开关

钩子在配置中以特性开关控制,默认启用

[features]
hooks = true

只有在明确不希望有任何生命周期脚本的情况下才应关闭:

[features]
hooks = false

features.hooks 也收录在 docs/zh/config-reference.md 的特性开关表中(默认值为 on)。关闭它意味着本次运行完全不会发现、校验或执行任何钩子。


钩子存放在哪里:配置层级与发现规则

Open Interpreter 在活动配置层旁边(next to active config layers)发现钩子。也就是说,钩子不是散落在任意目录,而是跟随用户级与项目级(受信任的)配置层一起被发现:

位置 作用域
~/.openinterpreter/hooks.json 用户
~/.openinterpreter/config.toml 用户内联钩子
.openinterpreter/hooks.json 受信任的项目
.openinterpreter/config.toml 受信任的项目内联钩子
Enabled plugins 插件捆绑的钩子

两个要点:

  1. 多来源并存时全部运行。文档明确:如果匹配到多个来源,它们全部运行;
  2. 高优先级配置不会替代低优先级层。即使上层配置中存在同名钩子组,低层来源的钩子依旧会被执行。

这一点在源码的“发现”流程中也有对应结构:钩子发现逻辑会遍历配置层栈(ConfigLayerStack,从低到高逐层收集 handlers),再叠加 config.toml 内联钩子、同目录的 hooks.json,以及由已启用插件声明的插件钩子来源(PluginHookSource)。每个来源最终都会转换为一个或多个 ConfiguredHandler 参与调度,相关实现见 codex-rs/hooks/src/engine/discovery.rs

内联配置与独立文件:两种写法等价

钩子既可以内联写在 config.toml[hooks] 段下,也可以放在与配置层同目录的 hooks.json 中(见 docs/zh/config-reference.md “钩子”小节)。两种格式的数据模型完全一致(事件名 → 匹配器组 → 处理函数列表),下文给出 JSON 与 TOML 的对应写法。


信任模型:未受管理的命令钩子必须先审查、后信任

钩子会以你的权限执行外部命令,因此 Open Interpreter 对“非受管理(non-managed)的命令钩子”建立了信任门槛:

  • 运行前必须经过审查并获得信任
  • Open Interpreter 针对“精确的钩子定义”记录信任状态——换句话说,只要钩子的命令、参数或定义有任何一处变更,就需要重新审查、重新信任

在会话内,你可以使用斜杠命令管理钩子:

/hooks

对于已经通过外部自动化流程验证过钩子的场景,可以传入命令行参数跳过信任交互,但这应当很少使用

--dangerously-bypass-hook-trust

从配置源码看,信任状态沉淀在钩子状态表(HookStateToml)中,每条记录包含 enabled(是否启用)与 trusted_hash(定义哈希,用于探测定义变更)两个字段,见 codex-rs/config/src/hook_config.rs。这正是“定义变更即需重新信任”的底层依据:状态以定义哈希为键,哈希不匹配便视为新的待信任定义。

插件声明的钩子

“Enabled plugins(插件捆绑的钩子)”属于被信任来源的一部分。从源码测试可以看到插件钩子会生成带来源标识的持久化键(hook key),其格式形如:

{plugin_id}:{source_relative_path}:{event}:{group_index}:{handler_index}

例如 demo@test:hooks/hooks.json:pre_tool_use:0:0,把“哪个插件、哪个文件、哪个事件、哪一组、第几个处理器”精确地编码进信任记录,声明逻辑见 codex-rs/hooks/src/declarations.rs


生命周期事件一览

Open Interpreter 把钩子挂在代理生命周期的多个“事件”上。文档给出的事件表如下:

Event 触发时机
SessionStart 会话启动、恢复、清除或压缩时。
UserPromptSubmit 用户提示即将发送时。
PreToolUse 在支持的 shell、patch 或 MCP 工具运行之前。
PermissionRequest 在显示批准提示之前。
PostToolUse 在支持的 shell、patch 或 MCP 工具完成之后。
PreCompact 在上下文压缩之前。
PostCompact 在上下文压缩之后。
SubagentStart 子代理启动时。
SubagentStop 子代理停止时。
Stop 回合即将结束时。

源码层面的补充:从配置数据结构 HookEventsToml 与事件实现目录看,实际事件集合还会出现 SessionEnd(会话结束)一项——它被定义在 codex-rs/config/src/hook_config.rs 的枚举字段中,并有独立的输入协议 codex-rs/hooks/schema/generated/session-end.command.input.schema.json 与实现文件 codex-rs/hooks/src/events/session_end.rs(其实现中还定义了 SESSION_END_DEFAULT_TIMEOUT_SEC / SESSION_END_MAX_TIMEOUT_SEC 等专用超时常量)。因此在配置事件时,除上述表格中的事件外,SessionEnd 也是一个合法的配置键(配置解析见 into_matcher_groups 对 11 个事件的一一映射)。

事件在架构上大致可以分为三类能力:

  1. 可阻断/拦截型:如 PreToolUse 可以拒绝一次工具调用,SessionStart 可通过输出协议中止本轮;
  2. 可注入上下文型:部分事件(如 SessionStartSubagentStart)可以把 stdout 或 additionalContext 变成模型可见的上下文
  3. 纯观测/记录型:多数事件都可用于把运行信息输出到日志/审计通道。

每类事件的触发结构都有独立的 Rust 事件实现文件(PreToolUsePostToolUseSessionStartSessionEndUserPromptSubmitPermissionRequestCompactStop、子代理起止等),统一放在 codex-rs/hooks/src/events/ 下。


配置文件格式:JSON 形态与 TOML 形态

JSON Form(hooks.json)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "^Bash$",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .openinterpreter/hooks/pre_tool_use.py",
            "timeout": 30,
            "statusMessage": "Checking command"
          }
        ]
      }
    ]
  }
}

TOML Form(config.toml 内联,与 hooks.json 完全等价)

[[hooks.PreToolUse]]
matcher = "^Bash$"

[[hooks.PreToolUse.hooks]]
type = "command"
command = "python3 .openinterpreter/hooks/pre_tool_use.py"
timeout = 30
statusMessage = "Checking command"

上面的 TOML 写法原样出现在 docs/zh/config-reference.md 的“钩子”小节中,说明它可以直接作为 config.toml 内联配置使用。

处理函数(handler)字段详解

codex-rs/config/src/hook_config.rs 的配置模型看,type = "command" 的处理函数支持以下字段:

字段 类型 说明
type 字符串 处理器类型,command(执行外部命令)。配置枚举中还存在 promptagent 两种声明类型。
command 字符串 要执行的命令,例如 python3 .openinterpreter/hooks/pre_tool_use.py
commandWindows(别名 command_windows 字符串(可选) 可选。为 Windows 平台单独指定的命令变体,便于跨平台部署同一份配置。
timeout(配置字段 timeout_sec 整数(可选) 可选。命令执行超时(秒)。默认没有限制时依赖引擎侧约定;钩子命令若超过超时会被终止并按失败处理。
statusMessage(配置字段 status_message 字符串(可选) 可选。运行期间的进度提示文案,例如 Checking command,会在交互界面展示给用户。
async 布尔 是否异步执行,默认 false
additionalContextLimit 整数(可选) 可选。该钩子 additionalContext 溢出落盘的近似 token 阈值。未设置时使用 2500 token;0 表示禁止该钩子落盘;阈值按原始上下文计算,落盘的预览内容还会附带恢复元数据。

对应的 JSON 反序列化模型同样支撑这些字段(字段序列化采用小驼峰,与上文 TOML 示例中的 statusMessagecommandWindows 一致),完整结构见 codex-rs/config/src/hook_config.rs

结构提示:一组“匹配器 + 若干处理器”被称为 MatcherGroupmatcher 为可选项,可省略),同事件下可配置多个 MatcherGroup。从源码结构看,省略 matcher 意味着该组钩子对该事件的所有实例生效;而命中同一事件同一 matcher 的多个来源(用户层、项目层、插件层)会全部运行而非互相覆盖。


匹配器(Matchers):用正则精确圈定触发范围

匹配器是正则表达式,用于把事件限制到你关心的子集。文档明确支持的匹配目标包括:

  • 工具事件PreToolUse / PostToolUse / PermissionRequest 等与工具相关的):匹配工具名称
  • SessionStart:匹配 startup|resume|clear|compact 四种触发来源;
  • 压缩事件PreCompact / PostCompact):匹配 manual|auto

从源码实现看,SessionStart 的四种来源对应枚举 SessionStartSourceStartup / Resume / Clear / Compact,其 as_str() 恰为 startup / resume / clear / compact),并被作为该事件的 matcher 输入参与调度,见 codex-rs/hooks/src/events/session_start.rs

文档给出的正则示例:

Bash
^apply_patch$
Edit|Write
mcp__filesystem__read_file
startup|resume
manual|auto

其中:

  • Bash 匹配工具名为 Bash 的工具调用(未锚定,等价于“名称中包含 Bash”也能命中,可用 ^Bash$ 精确限定);
  • ^apply_patch$ 精确匹配 apply_patch 补丁工具;
  • Edit|Write 匹配文件编辑类工具(EditWrite);
  • mcp__filesystem__read_file 演示了 MCP 工具在事件中的命名形态(MCP 工具前缀 + 服务器名 + 工具名);
  • startup|resumeSessionStart 钩子只在新会话启动与恢复时触发;
  • manual|auto 让压缩钩子同时覆盖手动压缩与自动压缩。

由于是正则表达式,你也可以组合使用,例如 ^(Bash|apply_patch)$ 同时圈住 shell 与补丁两类工具。事件实现中,调度器(dispatcher)会先按“事件名 + matcher 输入”选出命中的处理器(handlers),再逐个执行——匹配失败则该事件下没有任何钩子运行(见 codex-rs/hooks/src/events/session_start.rsrun 函数中 matched.is_empty() 早退分支)。


命令钩子的输入与输出协议

输入:一行 stdin JSON

命令钩子会在 stdin 上收到一个 JSON 对象,包含通用字段与会话/事件相关字段。文档列出的通用字段包括:session_idcwdhook_event_namemodel,以及事件特有字段。

PreToolUse 的输入协议为例,其 JSON Schema 定义在 codex-rs/hooks/schema/generated/pre-tool-use.command.input.schema.json,完整字段如下:

字段 类型 说明
session_id string 会话/线程 ID
cwd string 工作目录
hook_event_name string 固定为 PreToolUse
model string 当前使用的模型
permission_mode string(枚举) default / acceptEdits / plan / dontAsk / bypassPermissions
tool_name string 工具名称(如 Bashapply_patch 或 MCP 工具名)
tool_use_id string 本次工具调用的唯一 ID
tool_input object 工具入参。shell 类工具形如 { "command": "..." },MCP 工具为解析后的 JSON 参数
turn_id string 当前轮次 ID
agent_id / agent_type string 子代理场景下的代理标识(PreToolUseRequest 的 subagent 上下文中序列化而来)
transcript_path string/null(可选) 会话转录文件的路径

实现上,PreToolUse 的 stdin 由 codex-rs/hooks/src/events/pre_tool_use.rscommand_input_json 序列化:handler 选择时可能使用内部 matcher 别名,但 stdin 中始终保留规范化的 tool_name,从而保证审计日志与下游策略判定稳定。

一个典型的钩子脚本开头可以这样解析输入(Python 示例,对应文档中的 python3 .openinterpreter/hooks/pre_tool_use.py):

import json
import sys

payload = json.load(sys.stdin)
print(f"[hook] {payload['hook_event_name']} "
      f"session={payload['session_id']} tool={payload['tool_name']}",
      file=sys.stderr)

输出:stdout 的含义取决于事件与解析器

钩子退出后,引擎会根据退出码、stdout/stderr 内容与事件类型做统一解释,规则可归纳为:

  • 退出码 0:命令成功。此时对 stdout 的解读取决于事件:
    • 若 stdout 为空:无副作用;
    • 若 stdout 是合法的事件 JSON 输出:按事件协议处理(阻断、注入上下文、改写入参等);
    • 若 stdout 形似 JSON 但非法:该次钩子运行会被标记为失败(错误信息如 hook returned invalid pre-tool-use JSON output),避免把损坏的 JSON 当普通文本注入;
    • 在支持“上下文注入”的事件(如 SessionStart / SubagentStart)中,普通纯文本 stdout 会被直接当作模型上下文(源码测试 plain_stdout_becomes_model_context 即验证了此行为)。
  • 退出码 2:对 PreToolUse 而言是一种内置阻断语义——stderr 中的非空文本会被作为阻断原因(见 codex-rs/hooks/src/events/pre_tool_use.rs Some(2) 分支;文档未显式描述,属于实现层约定,可直接理解为“以退出码表达拒绝”的轻量写法);
  • 其他非零退出码:钩子运行失败,按该事件策略记录错误(错误文本形如 hook exited with code {exit_code});
  • 进程被信号终止或无状态码:记为失败。

这些解析规则沉淀在钩子引擎的 output_parser 与各事件的 parse_completed 中,并有成体系的单元测试(如 codex-rs/hooks/src/events/pre_tool_use.rs 内的 tests 模块、codex-rs/hooks/src/events/session_start.rs 内的 tests 模块)逐条验证退出码与 JSON 组合的判定结果。

事件特定输出示例:PreToolUse 拒绝一次调用

某些事件可以阻止或拒绝工具调用。 例如,PreToolUse 钩子可以拒绝一次命令,只需在 stdout 输出如下 JSON:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Blocked by repository policy."
  }
}

该行为的官方输出协议见 codex-rs/hooks/schema/generated/pre-tool-use.command.output.schema.json,其关键字段包括:

  • 顶层:systemMessage(告警文案)、suppressOutputstopReasoncontinue(默认 true);
  • 顶层兼容字段:decisionapprove / block)、reason(旧版兼容形态);
  • hookSpecificOutput 下:
    • hookEventName(必填,PreToolUse);
    • permissionDecisionallow / deny / ask
    • permissionDecisionReason:决策原因,会作为反馈展示给模型/用户;
    • additionalContext:注入模型可见上下文的文本;
    • updatedInput改写后的工具入参

对照事件实现的解析逻辑与单元测试,可以确认以下可验证行为(codex-rs/hooks/src/events/pre_tool_use.rs):

  • permissionDecision = "deny" → 本次调用被阻断(should_block = true),permissionDecisionReason 作为阻断原因与反馈记录(测试 permission_decision_deny_blocks_processing);
  • permissionDecision = "allow" 并携带 updatedInput → 放行,且用改写后的入参执行(测试 permission_decision_allow_can_update_input,例如把 {"command":"echo hello"} 改写为 {"command":"echo rewritten"});
  • 多个钩子同时给出 updatedInput 时,按实际完成顺序取最后完成的那个改写latest_updated_input,测试 last_completed_updated_input_winscompletion_order 验证),而非按配置顺序——用于审计展示的顺序保持稳定,但改写竞争以完成时序为准;
  • 返回不支持的决策组合(如 permissionDecision = "ask",或旧版 decision: approve 而无有效 allow 语义)→ 运行被记录为失败,但不阻断本次调用(测试中命名为 fails open);
  • 旧版形态 decision: "block" + reason 仍然有效,会被映射为阻断。

一句话总结:deny 是硬阻断,allow + updatedInput 是软改写,其余非法组合按失败记录但放行(fail-open)。

事件特定输出示例:SessionStart 注入上下文与停止

SessionStart / SubagentStart 属于“可以向模型可见上下文添加内容”的事件。其输出协议见 codex-rs/hooks/schema/generated/session-start.command.output.schema.jsonhookSpecificOutput 内含 additionalContext(模型可见的注入文本),顶层另有 stopReasonsuppressOutputsystemMessagecontinue 等字段。

实现层的行为(codex-rs/hooks/src/events/session_start.rs):

  • 普通 stdout 直接成为模型上下文:只要退出码为 0 且 stdout 不是 JSON,stdout 全文(去空白)即注入为上下文(测试 plain_stdout_becomes_model_context);
  • continue: false 可以停止本轮SessionStart 钩子返回 {"continue":false,"stopReason":"pause",...} 时,会话会暂停(HookRunStatus::Stopped),同时 additionalContext 仍被保留供后续轮次使用(测试 continue_false_preserves_context_for_later_turns 注释“do not inject”恰好演示了这一点);
  • SubagentStart 只做上下文注入:从实现注释与测试看,SubagentStart 忽略 continue:false(测试 subagent_start_continue_false_is_ignored),即子代理启动钩子仅负责注入子代理上下文,不承担中止职责。

大上下文溢出处理(additional context spilling)

当钩子注入的上下文很大时,Open Interpreter 并不会无限膨胀模型上下文。实现里存在一个“溢出落盘(spill)”通道(codex-rs/hooks/src/output_spill.rs):在输出 schema 允许 additionalContext 的事件中,注入文本会先按 token 阈值评估——未显式配置时阈值默认约 2500 token(DEFAULT_HOOK_OUTPUT_TOKEN_LIMIT,常量在引擎中发现/执行路径中引用),超出部分被溢出保存,模型侧只保留带恢复元数据的预览。你可以在命令处理器上通过 additionalContextLimit 字段覆盖阈值(0 表示禁止溢出、始终全量注入),这也是“确定性 + 可控性”的体现。


结合仓库源码理解整体架构

hooks 功能对应的主要源码位置(仓库根目录下相对路径):

关注点 路径
官方中文文档(本文主体) docs/zh/hooks.md
英文原档 docs/hooks.md
钩子引擎:发现、调度、命令执行、输出解析 codex-rs/hooks/src/engine/discovery.rs / dispatcher.rs / command_runner.rs / output_parser.rs
各生命周期事件实现 codex-rs/hooks/src/events/
钩子输入/输出 JSON Schema codex-rs/hooks/schema/generated/
钩子配置数据模型(JSON/TOML/内联/状态) codex-rs/config/src/hook_config.rs
插件钩子声明与持久化键 codex-rs/hooks/src/declarations.rs
上下文溢出落盘 codex-rs/hooks/src/output_spill.rs
配置参考文档(含内联钩子示例与 features.hooks docs/zh/config-reference.md

从源码结构可以勾勒出一次钩子运行的完整链路:

  1. 发现(discovery)discover_handlers 遍历配置层栈,把内联 config.toml 钩子、同目录 hooks.json、受管理钩子要求(managed hooks)与插件钩子全部物化为 ConfiguredHandler(每个处理器携带事件名、matcher、命令、超时、来源与展示顺序),见 codex-rs/hooks/src/engine/discovery.rs
  2. 选择(dispatch):事件触发时,调度器按“事件名 + matcher 输入”筛选命中的处理器(事件实现中以 preview() 生成待运行摘要、run() 实际执行),见 codex-rs/hooks/src/events/session_start.rscodex-rs/hooks/src/engine/dispatcher.rs
  3. 执行与解析(execute & parse)command_runner 以 cwd 为基准运行命令并把 stdin JSON 交给脚本,随后按上文输出规则解析退出码/stdout,产出每一条 HookCompletedEvent(含状态 Completed / Failed / Blocked / Stopped)与最终结果(是否阻断、注入哪些上下文、是否改写入参)。

这些“事件 → 匹配器组 → 处理器 → 结果聚合”的每一步都有对应单元测试覆盖,例如 codex-rs/hooks/src/events/session_start.rs 的 tests 模块、codex-rs/hooks/src/events/pre_tool_use.rs 的 tests 模块(含插件钩子键格式验证、permissionDecision 语义矩阵、改写竞争等),可作为你自建钩子的行为参考。


实操建议与边界提醒

  1. 把钩子当作“策略层”而非“功能层”:日志、审计、扫描、注入这类确定性职责非常适合钩子;而应当由模型自主决策的内容不建议硬编码进钩子。
  2. 为钩子命令显式设置超时:为每个 type = "command" 处理器配置合理的 timeout,避免某个钩子脚本卡死整轮代理动作;跨平台部署时可用 commandWindows 为 Windows 提供等价命令。
  3. 正确理解 trust 边界:修改钩子定义后记得重新执行 /hooks 审查并信任新定义;仅在外部已做同等验证时才使用 --dangerously-bypass-hook-trust
  4. 用 matcher 收敛触发面:尽量使用锚定正则(如 ^Bash$)精确圈定要拦截的工具,避免宽泛匹配拖慢每一次工具调用。
  5. 组合使用安全体系:再次强调文档结论——钩子是安全防护措施,而不是沙箱和批准的替代方案。需要强隔离的执行环境时,应配合 Open Interpreter 的沙箱与批准机制,形成“批准先行、沙箱兜底、钩子审计/拦截”的多层防线。

掌握以上配置语法、事件语义与协议细节之后,你就可以把任意受信任的确定性逻辑(策略、日志、扫描、上下文、运行后校验)精确嵌入到 Open Interpreter 的每一步生命周期事件中。

登录后查看全文
热门项目推荐
相关项目推荐