首页
/ Open Interpreter Hooks 生命周期钩子完全指南:事件、信任模型与源码级原理

Open Interpreter Hooks 生命周期钩子完全指南:事件、信任模型与源码级原理

2026-09-06 18:17:08作者:郦嵘贵Just

Hooks 是 Open Interpreter 提供的一套轻量级生命周期钩子机制:在 Agent 会话启动、用户提交 Prompt、工具调用前后、上下文压缩等关键节点,执行确定性的外部命令脚本,用于策略检查、审计日志、Prompt 扫描、自定义上下文注入与事后校验。读完本文,你将掌握 Hooks 的启用方式、配置文件的放置规则、全部事件与 matcher 写法、JSON/TOML 声明格式,以及命令 Hook 与模型之间的输入输出协议,并从仓库源码理解其发现、信任与调度实现。

本指南以 docs/hooks.md 为纲,结合 Open Interpreter 仓库中 hooks 功能模块的 Rust 实现(codex-rs/hooks)展开,帮助你在真实项目中正确使用 Hooks 作为"护栏",而不是替代沙箱与审批。

Hooks 是什么:为 Agent 生命周期挂载确定性脚本

Hooks 让你围绕 Open Interpreter 的事件(events)运行确定性的命令(deterministic commands)。它非常适合但不限于以下四类诉求:

  • 策略检查:在危险工具执行前按正则拦截或审批;
  • 日志/审计:记录每一次工具调用、Prompt 提交与会话事件;
  • Prompt 扫描:对即将发给模型的用户输入做敏感词、越狱检测;
  • 自定义上下文注入 / 事后校验:把外部系统信息喂给模型,或在运行结束后做结果验证。

它的定位与 Sandbox 不同:文档明确指出 Hooks 是护栏(guardrails),不是沙箱(sandboxing)与审批(approvals)的替代品——真正要隔离环境,请配合仓库中的 Linux/macOS/Windows 沙箱能力使用。

默认开启与全局开关

Hooks 默认启用,对应配置项位于 config 的 [features] 段:

[features]
hooks = true

只有当你有意希望整个 Agent 生命周期不运行任何脚本时,才关闭它:

[features]
hooks = false

关闭后,仓库中 hooks 引擎将不再发现与派发任何钩子处理函数。

Hooks 存放在哪里:发现位置与叠加规则

Open Interpreter 会在当前生效的配置层(config layers)旁边发现 hooks。文档给出的候选位置与作用域如下:

位置 作用域
~/.openinterpreter/hooks.json 用户级(User)
~/.openinterpreter/config.toml 用户级内联 hooks(User inline)
.openinterpreter/hooks.json 受信任项目(Trusted project)
.openinterpreter/config.toml 受信任项目内联 hooks(Trusted project inline)
已启用的插件 插件自带 hooks(Plugin-bundled)

关键语义:多个来源同时匹配时,它们会全部执行。更高优先级的配置层不会替换掉低优先级层的 hooks——Hooks 是"叠加"而非"覆盖"的。这一行为在源码中被精确实现:发现逻辑 discover_handlers 遍历 config_layer_stack.layers_low_to_high(),从低优先级层到高优先级层依次收集每个层级的 hooks 处理器,全部汇入同一个处理函数列表(见 codex-rs/hooks/src/engine/discovery.rs)。

另外从源码看,一个层内有两种声明载体(hooks.jsonconfig.toml 内联),若同一层同时提供两者且都非空,引擎会给出类似 "loading hooks from both ... prefer a single representation for this layer" 的告警——因此建议同一配置层只选用其中一种表示形式,避免定义重复。

信任模型:非托管 Hook 必须先审查

Hooks 会执行任意命令,因此存在代码执行风险。Open Interpreter 的信任规则如下:

  • 非托管的命令 hooks(non-managed command hooks)必须先经过人工审查并标记为受信任,才会被执行
  • 信任关系是基于 Hook 的精确定义(exact hook definition)记录的——即命令字符串、事件、matcher 等组成的完整定义。一旦 Hook 定义发生变化(哪怕只改了一个参数),就需要重新审查,旧信任不自动生效;
  • 使用斜杠命令审查与开关:
    /hooks
    

若存在已在外部完成 hooks 校验的自动化流水线,可以传入 --dangerously-bypass-hook-trust 跳过信任检查,但文档强调这应当极其罕见。对应到实现,HookDiscoveryPolicy 中有一个 bypass_hook_trust 开关,另有一个 allow_managed_hooks_only 策略(当配置层要求仅允许托管 hooks 时,非托管来源会被直接过滤),见 codex-rs/hooks/src/engine/discovery.rs。此外,与 hooks.json 并列的还有托管 hooks(managed hooks)概念,来自配置层的 managed_hooks 需求,它们以托管来源注入,不受"需手动信任"约束。

支持的事件(Events)

Hook 引擎在 Agent 生命周期的十个节点提供事件挂钩:

事件 触发时机 matcher 目标
SessionStart 会话启动、恢复(resume)、清空(clear)或压缩(compact)时 startup|resume|clear|compact
UserPromptSubmit 用户 prompt 即将发送给模型时 无 / 全部
PreToolUse 受支持的 shell、patch 或 MCP 工具运行之前 工具名
PermissionRequest 审批弹窗显示之前 无 / 全部
PostToolUse 受支持的 shell、patch 或 MCP 工具执行完毕之后 工具名
PreCompact 上下文压缩开始之前 manual|auto
PostCompact 上下文压缩完成之后 manual|auto
SubagentStart 子代理(subagent)启动时 agent_type
SubagentStop 子代理停止时 agent_type
Stop 一轮对话(turn)即将结束时 无 / 全部

从源码结构看,每个事件都有对应的独立 Rust 模块实现事件特有的输入构造与结果聚合逻辑(见 codex-rs/hooks/src/events)。例如 SessionStart 的触发来源在源码中建模为枚举 SessionStartSource { Startup, Resume, Clear, Compact },其字符串形式正是 matcher 使用的 startupresumeclearcompact(见 codex-rs/hooks/src/events/session_start.rs)。SessionStart 事件同样在 SubagentStart 时通过共享的 StartHookTarget 机制触发,说明子代理的启动本质上复用"会话类"事件管线。

声明 Hooks:JSON 与 TOML 两种形式

JSON 形式(hooks.json)

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

TOML 形式(config.toml 内联)

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

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

两种结构等价。结构层级为:hooks → 事件名 → matcher 分组 → hooks 列表。常用字段说明:

  • matcher:正则表达式,决定该组 hook 命中哪些目标(见下节);
  • type:Hook 类型,command 即执行外部命令行程序;
  • command:要执行的命令行(支持带参数,如 python3 ...);
  • timeout:命令超时秒数,超时视为失败处理;
  • statusMessage:hook 运行时在终端展示的状态提示,例如 "Checking command",让用户感知到有校验在发生。

多个来源全部执行

再次强调叠加语义:若用户级与项目级同时声明了同一事件下的 hooks,它们都会运行,不会因配置层优先级更高而吞掉低层级的 Hook。

Matcher 匹配规则

Matcher 是正则表达式,对每个事件匹配不同目标:

  • 工具类事件PreToolUsePostToolUse)匹配工具名。例如 ^Bash$ 只命中 shell 工具;
  • SessionStart 匹配 startup|resume|clear|compact 四种来源;
  • 压缩类事件PreCompactPostCompact)匹配 manual|auto,区分手工触发与自动压缩。

文档给出的 matcher 示例:

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

其中 Edit|Write 表示同时命中文件编辑与写入两类工具,mcp__filesystem__read_file 说明 MCP 工具按 mcp__<server>__<tool> 的命名规则匹配,因此你也可以针对某个 MCP 服务器的特定工具做精细拦截。从源码结构看,matcher 表达式在事件公共层会经过校验与模式编译处理(见 codex-rs/hooks/src/events/common.rs 中 matcher 相关逻辑,如 matcher_pattern_for_eventvalidate_matcher_pattern),非法正则不会被静默吞掉。

为什么 PreToolUse 能感知多个"别名"

pre_tool_use.rs 中,PreToolUseRequest 除了 tool_name 还带有 matcher_aliases: Vec<String>;派发前,引擎会对工具名与所有别名执行 matcher_inputs 展开(见同文件 preview/runcommon::matcher_inputs),再进行 select_handlers_for_matcher_inputs 选择命中分组的 handler。这就是为什么同一工具可能被多组 matcher 同时命中——所有命中的分组都会执行。

Hook 输入与输出协议

命令型 Hook(type = "command")的运行协议是通过 stdin 接收一个 JSON 对象,通过 stdout 返回 JSON 结果(引擎侧通过输出解析器读取)。

输入:stdin 上的 JSON 对象

所有事件共享的基础字段包括:session_idcwdhook_event_namemodel,以及各事件特有的字段。以仓库中自动生成的 pre-tool-use.command.input 校验模式(见 codex-rs/hooks/schema/generated/pre-tool-use.command.input.schema.json)为例,PreToolUse 事件的输入完整字段为:

  • 通用字段:session_idturn_idcwdtranscript_pathmodelpermission_modeagent_idagent_type
  • 事件特有字段:tool_nametool_use_idtool_input(工具调用的原始输入 JSON,schema 中该字段类型为 true,即任意 JSON);
  • hook_event_name 为常量 "PreToolUse"
  • permission_mode 是枚举:defaultacceptEditsplandontAskbypassPermissions,对应 Open Interpreter 的权限模式。

UserPromptSubmit 事件的输入则在此基础上携带 prompt 字段(用户原始输入文本),便于做 Prompt 扫描与改写(见 codex-rs/hooks/schema/generated/user-prompt-submit.command.input.schema.json)。

输出:阻止 / 补充上下文 / 改写输入

不同事件对输出能力的支持不同:

  • 部分事件可以向模型追加可见上下文(additional context):将自定义信息注入后续对话;
  • 部分事件可以阻止或拒绝一次工具调用(block/deny),例如 PreToolUse

文档给出的"拒绝一次命令执行"输出示例:

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

结合源码中自动生成的输出 schema(见 codex-rs/hooks/schema/generated/pre-tool-use.command.output.schema.json),PreToolUse 的完整输出契约实际上包括:

  • hookSpecificOutput.permissionDecisionallow / deny / ask——deny 直接拦截工具执行;ask 可把决策重新抛回审批流程;
  • hookSpecificOutput.permissionDecisionReason:决策原因,将透传给模型与界面;
  • hookSpecificOutput.additionalContext:追加给模型的可见上下文;
  • hookSpecificOutput.updatedInput:改写后的工具入参;
  • hookSpecificOutput.hookEventName:必填,用于区分输出归属的事件;
  • 顶层另有 decisionapprove / block)、reasoncontinue(默认 true,决定后续 hook 是否继续执行)、suppressOutputsystemMessagestopReason 等字段,供更精细地控制流程。

这类 JSON Schema 由仓库按事件逐一维护(见 codex-rs/hooks/schema/generated 目录下各 *.input.schema.json / *.output.schema.json),是编写 Hook 脚本时最权威的字段字典。

在引擎实现上,PreToolUse 事件运行时会聚合所有命中 handler 的结果:只要任一 handler 标记 should_block,该工具调用即被阻止,并携带首个阻断原因(见 codex-rs/hooks/src/events/pre_tool_use.rs)。这就是"多来源 hooks 全部执行、任一否决即拦截"的执行语义。

源码视角:Hooks 引擎的四个环节

仓库将 hooks 功能实现为独立的 Rust crate(见 codex-rs/hooks/Cargo.toml),整体可拆为四条链路:

  1. 发现(Discovery)discover_handlers 按配置层栈低到高遍历,加载每个层级的 hooks.jsonconfig.toml 内联 hooks,追加托管 hooks 与插件 hooks(插件还会注入 PLUGIN_ROOTPLUGIN_DATA 等环境变量供脚本使用),最终输出 handler 列表与展示条目,见 engine/discovery.rs
  2. 调度(Dispatcher)select_handlers_for_matcher_inputs 等函数按事件名与 matcher 输入选出命中的 handler 集合,见 engine/dispatcher.rs
  3. 执行(Command Runner):为每个命令 handler 创建进程、写入 stdin JSON、施加超时并收集 stdout/stderr 结果,见 engine/command_runner.rs
  4. 输出解析(Output Parser)与 Schema 加载:解析命令 stdout 中的 JSON,并依据 schema_loader.rsschema/generated 下的事件专属 schema 校验输入输出。

每个事件模块(events/pre_tool_use.rsevents/session_start.rs 等)都遵循同一模式:构造本事件请求 → 序列化输入 JSON → 派发执行 → 按事件语义聚合结果(阻断 / 补充上下文 / 改写输入 / 透传事件记录)。引擎侧同时维护 Hook 的运行状态(running/hook run summary),用于 /hooks 命令展示与状态管理,并在事件执行失败时以 FailedContinue / FailedAbort 语义区分"继续执行其余 hook"与"中止整个操作"。

端到端示例:用 PreToolUse 实现"仓库策略门禁"

把上述能力串成一个可直接上手的场景——禁止在项目仓库中运行 git push 之外的危险 shell 命令。假定这是受信任项目,将以下内容放入项目根的 .openinterpreter/hooks.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "^Bash$",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .openinterpreter/hooks/policy_gate.py",
            "timeout": 15,
            "statusMessage": "Checking against repo policy"
          }
        ]
      }
    ]
  }
}

对应脚本 .openinterpreter/hooks/policy_gate.py 的大致骨架:

#!/usr/bin/env python3
import json, re, sys

data = json.load(sys.stdin)          # 从 stdin 读取输入 JSON
cmd = json.loads(data["tool_input"].get("command", "{}")) if isinstance(data.get("tool_input"), dict) else ""
blocked = ["rm -rf", "git reset --hard", "curl .*\\|.*sh"]

if any(re.search(p, cmd) for p in blocked):
    print(json.dumps({
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "deny",
            "permissionDecisionReason": "Blocked by repository policy."
        }
    }))
else:
    print(json.dumps({"hookSpecificOutput": {"hookEventName": "PreToolUse"}}))

脚本按上文的输出契约,在命中策略时返回 deny 及理由,Open Interpreter 随即阻止该 shell 调用;未命中则返回空决策放行。首次执行前记得通过 /hooks 审查并信任该命令 Hook——改动脚本或配置后需重新信任。更精细的做法:用 additionalContext 注入项目专属约束、用 updatedInput 改写工具入参、用 ask 把敏感操作交回审批而非直接放行。

边界与最佳实践

  • Hooks ≠ 安全边界:它们运行在你的机器上、拥有与 Open Interpreter 进程相同的权限,应视为策略表达层。真正的隔离请依赖沙箱与权限审批体系。
  • 命令型 Hook 需可信hooks.json/脚本若被第三方篡改,等于任意代码执行入口,务必保持项目钩子文件与脚本的只读与版本受控。
  • 事件选择要克制PreToolUse/PostToolUse 在每个工具调用时都会触发,耗时的 Hook(如网络请求)会拖慢 Agent;合理设置 timeout,只在必要时挂载。
  • matcher 越精确越好:能用 ^Bash$^apply_patch$ 就用精确锚定,避免宽泛正则在每个工具调用上都白跑一遍。
  • JSON/TOML 选一种:同一配置层同时写两种内联表示会触发引擎告警,保持单一声明源。
  • 信任随定义变化失效:任何对已信任 Hook 的修改都会使其回到"未信任"状态,自动化发布流程应主动配合 /hooks 重新审查,或仅在可控环境中使用 --dangerously-bypass-hook-trust

结合 docs/hooks.mdcodex-rs/hooks 模块,你现在应能从声明格式到事件协议、再到内部调度与信任语义,完整掌控 Open Interpreter 的 Hooks 机制,并将其作为"会话策略层"接入自己的日志、审计与安全流程。

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