OpenAI Codex 中文教程

钩子

在 Codex 生命周期中运行确定性的脚本

钩子是 Codex 的一套扩展框架。它允许你把自己的脚本插入智能体循环中,从而实现例如以下能力:

  • 把对话发送到自定义日志或分析系统
  • 扫描团队提示词,阻止误粘贴 API key
  • 自动总结对话,生成持久记忆
  • 在 turn 结束时运行自定义校验检查,强制执行团队标准
  • 当工作目录匹配特定路径时,动态调整提示词策略

Hooks 默认启用。如果需要在 config.toml 中关闭,请设置:

[features]
hooks = false

请把 hooks 作为标准功能键。codex_hooks 仍可作为已弃用 alias 使用。

管理员也可以在 requirements.toml 中用同样方式强制关闭 hooks:[features].hooks = false

需要留意这些运行时行为:

  • 来自多个文件的匹配钩子都会运行。
  • 对同一事件命中的多个命令型钩子会并发启动,因此一个钩子不能阻止其他已命中的钩子启动。
  • 非托管命令型 hooks 必须经过审核并被信任后才会运行。
  • PreToolUsePermissionRequestPostToolUseUserPromptSubmitStop 都是按 turn 作用域运行的。

Codex 在哪里查找钩子

Codex 会在当前激活配置层旁边查找以下任一形式的钩子配置:

  • hooks.json
  • config.toml 中的内联 [hooks]

已安装插件也可以通过插件 manifest 或默认的 hooks/hooks.json 文件打包生命周期配置。插件打包规则请参见构建插件

实际使用中,最常见也最有用的四个位置是:

  • ~/.codex/hooks.json
  • ~/.codex/config.toml
  • <repo>/.codex/hooks.json
  • <repo>/.codex/config.toml

如果存在多个钩子来源,Codex 会加载所有命中的钩子。高优先级配置层不会替换低优先级配置层里的钩子。如果同一配置层同时包含 hooks.json 和内联 [hooks],Codex 会合并它们,并在启动时给出警告。建议每个配置层只使用其中一种表示方式。

本版本中 plugin hooks 默认关闭。如果设置了 [features].plugin_hooks = true,Codex 也可以发现已启用插件中打包的 hooks。否则,已启用插件不会运行其内置 hooks。

项目本地钩子只会在项目 .codex/ 配置层受信任时加载。在不受信任的项目中,Codex 仍会加载用户层和系统层中各自激活的钩子。

审核和管理 hooks

Codex 会在决定哪些 hooks 可以运行之前列出已配置 hooks。在 CLI 中使用 /hooks 可以检查 hook 来源、审核新增或已变更 hooks、信任 hooks,或禁用单个非托管 hook。如果启动时有 hooks 需要审核,Codex 会打印警告,提示你打开 /hooks

来自 system、MDM、cloud 或 requirements.toml 来源的 managed hooks 会被标记为托管,由策略信任,并且不能从用户 hook 浏览器中禁用。

配置结构

Hooks 分成三层:

  • 事件名,例如 PreToolUsePostToolUseStop
  • 决定该事件何时命中的 matcher 分组
  • 当 matcher 命中时实际执行的一个或多个钩子处理器
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup|resume",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.codex/hooks/session_start.py",
            "statusMessage": "Loading session notes"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/pre_tool_use_policy.py\"",
            "statusMessage": "Checking Bash command"
          }
        ]
      }
    ],
    "PermissionRequest": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/permission_request.py\"",
            "statusMessage": "Checking approval request"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/post_tool_use_review.py\"",
            "statusMessage": "Reviewing Bash output"
          }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/user_prompt_submit_data_flywheel.py\""
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/stop_continue.py\"",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

说明:

  • timeout 的单位是秒。
  • 如果省略 timeout,Codex 会使用 600 秒。
  • statusMessage 是可选的。
  • async 会被解析,但当前还不支持 async command hooks。Codex 会跳过带有 async: true 的处理器。
  • 目前只有 type: "command" 的处理器会运行。promptagent 处理器会被解析,但会被跳过。
  • 命令会以当前会话的 cwd 作为工作目录运行。
  • 对仓库级钩子,优先使用基于 Git 根目录解析的路径,而不是 .codex/hooks/... 这类相对路径。Codex 可能从子目录启动,基于 Git 根目录的写法更稳定。

config.toml 中等价的内联 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"

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

[[hooks.PostToolUse.hooks]]
type = "command"
command = '/usr/bin/python3 "$(git rev-parse --show-toplevel)/.codex/hooks/post_tool_use_review.py"'
timeout = 30
statusMessage = "Reviewing Bash output"

来自 requirements.toml 的托管钩子

企业托管的 requirements 也可以在 [hooks] 下内联定义钩子。当管理员希望强制执行钩子配置,同时通过 MDM 或其他设备管理系统分发实际脚本时,这种方式很有用。若要即使用户在本地关闭 hooks 也强制执行托管 hooks,请在 requirements.toml 中把 [features].hooks = true[hooks] 一起固定下来。

[features]
hooks = true

[hooks]
managed_dir = "/enterprise/hooks"
windows_managed_dir = 'C:\enterprise\hooks'

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

[[hooks.PreToolUse.hooks]]
type = "command"
command = "python3 /enterprise/hooks/pre_tool_use_policy.py"
timeout = 30
statusMessage = "Checking managed Bash command"

托管钩子的注意事项:

  • managed_dir 用于 macOS 和 Linux。
  • windows_managed_dir 用于 Windows。
  • Codex 不会分发 managed_dir 中的脚本;你的企业管理工具需要单独安装并更新这些脚本。
  • 托管钩子命令应使用配置的托管目录下的绝对脚本路径。

插件打包的 hooks

本版本中 plugin-bundled hooks 需要显式开启。当 [features].plugin_hooks = true 且插件已启用时,Codex 可以把该插件里的生命周期 hooks 与用户、项目和托管 hooks 一起加载。

[features]
plugin_hooks = true

默认情况下,Codex 会在插件根目录中查找 hooks/hooks.json。插件 manifest 可以通过 .codex-plugin/plugin.json 中的 hooks 条目覆盖这个默认位置。manifest 条目可以是一个 ./ 前缀路径、./ 前缀路径数组、内联 hooks 对象,或内联 hooks 对象数组。

{
  "name": "repo-policy",
  "hooks": "./hooks/hooks.json"
}

Manifest hook 路径会相对于插件根目录解析,并且必须留在该根目录内。如果 manifest 定义了 hooks,Codex 会使用这些 manifest 条目,而不是默认的 hooks/hooks.json

Plugin hook 命令会收到这些环境变量:

  • PLUGIN_ROOT 是 Codex 专用扩展,指向已安装插件根目录。
  • PLUGIN_DATA 是 Codex 专用扩展,指向插件的可写数据目录。
  • 为了兼容现有 plugin hooks,Codex 还会设置 CLAUDE_PLUGIN_ROOTCLAUDE_PLUGIN_DATA

Plugin hooks 使用和其他 hooks 相同的事件 schema。它们属于非托管 hooks,因此运行前需要经过信任审核。

匹配器模式

matcher 字段是一个正则表达式字符串,用来过滤 hook 何时触发。使用 "*""",或完全省略 matcher,都表示匹配该事件的所有支持触发。

当前只有部分 Codex 事件真正会使用 matcher

事件 matcher 过滤的对象 说明
PermissionRequest 工具名 支持 Bashapply_patch* 和 MCP tool names。
PostToolUse 工具名 支持 Bashapply_patch* 和 MCP tool names。
PreToolUse 工具名 支持 Bashapply_patch* 和 MCP tool names。
SessionStart 启动来源 当前运行时的值包括 startupresumeclear
UserPromptSubmit 不支持 该事件中配置的 matcher 会被忽略。
Stop 不支持 该事件中配置的 matcher 会被忽略。

* 对 apply_patchmatcher 值也可以使用 EditWrite

示例:

  • Bash
  • ^apply_patch$
  • Edit|Write
  • mcp__filesystem__read_file
  • mcp__filesystem__.*
  • startup|resume|clear

通用输入字段

每个命令型钩子都会通过 stdin 收到一个 JSON 对象。

这些共享字段通常最常用:

字段 类型 含义
session_id string 当前 session 或 thread id。
transcript_path string | null session transcript 文件路径;如果不存在则为 null
cwd string 当前会话的工作目录。
hook_event_name string 当前 hook 事件名。
model string Codex 扩展字段。当前激活模型的 slug。

按 turn 作用域运行的 hooks 会在各自的事件专属字段表里额外列出 turn_id

SessionStartPreToolUsePermissionRequestPostToolUseUserPromptSubmitStop 也会包含 permission_mode,表示当前权限模式,值可能是 defaultacceptEditsplandontAskbypassPermissions

transcript_path 只是为了方便指向对话 transcript;transcript 格式不是 hooks 的稳定接口,未来可能变化。

如果你需要完整的当前线格式,请参见 Schema 定义

通用输出字段

SessionStartUserPromptSubmitStop 支持以下共享 JSON 字段:

{
  "continue": true,
  "stopReason": "optional",
  "systemMessage": "optional",
  "suppressOutput": false
}
字段 作用
continue 若为 false,表示该次 hook 运行被标记为停止。
stopReason 记录为停止原因。
systemMessage 作为警告显示在界面或事件流中。
suppressOutput 当前会被解析,但尚未真正实现。

退出码为 0 且没有任何输出,会被视为成功,Codex 会继续执行。

PreToolUsePermissionRequest 支持 systemMessage,但当前不支持 continuestopReasonsuppressOutput。如果 PreToolUse hook 返回了这些不支持的字段,Codex 会把这次 hook 运行标记为失败、报告错误,并继续执行工具调用。

PostToolUse 支持 systemMessagecontinue: falsestopReasonsuppressOutput 虽然会被解析,但当前仍未真正支持。

Hooks

SessionStart

这个事件中的 matcher 会作用在 source 上。

通用输入字段 外,还会额外提供:

字段 类型 含义
source string 会话启动方式:startupresumeclear

写到 stdout 的纯文本会被追加为额外的 开发者上下文。

如果向 stdout 输出 JSON,则支持 通用输出字段,以及下面这个该事件专属结构:

{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Load the workspace conventions before editing."
  }
}

其中 additionalContext 会被加入为额外的 开发者上下文。

PreToolUse

PreToolUse 可以拦截 Bash、通过 apply_patch 完成的文件编辑,以及 MCP tool calls。它仍然是护栏,而不是完整的强制边界,因为 Codex 通常可以通过另一条受支持工具路径完成等价工作。

matcher 会作用在 tool_name 和 matcher aliases 上。对于通过 apply_patch 完成的文件编辑,matcher 值可以使用 apply_patchEditWrite;hook input 中仍会报告 tool_name: "apply_patch"

通用输入字段 外,还会额外提供:

字段 类型 含义
turn_id string Codex 扩展字段。当前激活 turn 的 id。
tool_name string 标准 hook 工具名,例如 Bashapply_patch,或 mcp__fs__read 这类 MCP 名称。
tool_use_id string 本次调用对应的 tool-call id。
tool_input JSON value 工具专属输入。Bashapply_patch 使用 tool_input.command,MCP tools 会发送所有参数。

写到 stdout 的纯文本会被忽略。

如果向 stdout 输出 JSON,可以使用 systemMessage,也可以通过下面这个事件专属结构阻止 Bash 命令执行:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Destructive command blocked by hook."
  }
}

Codex 也接受旧版阻止格式:

{
  "decision": "block",
  "reason": "Destructive command blocked by hook."
}

你也可以直接使用退出码 2,并把阻止原因写到 stderr

如需在不阻止调用的情况下追加模型可见上下文,请返回 hookSpecificOutput.additionalContext

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "additionalContext": "The pending command touches generated files."
  }
}

若要在不阻止调用的情况下改写受支持的工具调用,请返回 permissionDecision: "allow" 并附带 updatedInput

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "updatedInput": {
      "command": "echo rewritten"
    }
  }
}

对于 Bash 命令和 apply_patchupdatedInput 必须包含字符串类型的 command 字段。对于 MCP tools,updatedInput 是替换后的参数对象。只应在 permissionDecision: "allow" 时返回 updatedInput;其他 updatedInput 形态会被报告为错误。

permissionDecision: "ask"、旧版 decision: "approve"continue: falsestopReasonsuppressOutput 虽然会被解析,但目前尚未支持。Codex 会把这次 hook 运行标记为失败、报告错误,并继续执行工具调用。

PermissionRequest

PermissionRequest 会在 Codex 即将请求审批时运行,例如 shell 提权或托管网络审批。它可以允许请求、拒绝请求,或者不做决定并让常规审批提示继续显示。它不会在不需要审批的命令上运行。

matcher 会作用在 tool_name 和 matcher aliases 上。当前标准值包括 Bashapply_patch,以及 mcp__server__tool 这类 MCP tool names;apply_patch 也会匹配 EditWrite

通用输入字段 外,还会额外提供:

字段 类型 含义
turn_id string Codex 扩展字段。当前激活 turn 的 id。
tool_name string 标准 hook 工具名,例如 Bashapply_patch,或 mcp__fs__read 这类 MCP 名称。
tool_input JSON value 工具专属输入。Bashapply_patch 使用 tool_input.command,MCP tools 会发送所有参数。
tool_input.description string | null Codex 提供时的人类可读审批原因。

写到 stdout 的纯文本会被忽略。

某些工具输入可能包含人类可读的说明,但不要假设每个工具都会提供 tool_input.description 字段。

如需批准请求,请返回:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow"
    }
  }
}

如需拒绝请求,请返回:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "deny",
      "message": "Blocked by repository policy."
    }
  }
}

如果多个命中的 hook 都返回了 decision,任意 deny 都会优先生效。否则,一个 allow 会让请求继续执行,并且不再显示审批提示。如果没有命中的 hook 做出决定,Codex 会使用正常审批流程。

不要为 PermissionRequest 返回 updatedInputupdatedPermissionsinterrupt;这些字段预留给未来行为,目前会按关闭失败处理。

PostToolUse

PostToolUse 会在受支持工具产生输出后运行,包括 Bash、apply_patch 和 MCP tool calls。对 Bash 来说,命令以非零状态退出后也会运行。它无法撤销已经执行过的工具副作用。

matcher 会作用在 tool_name 和 matcher aliases 上。对于通过 apply_patch 完成的文件编辑,matcher 值可以使用 apply_patchEditWrite;hook input 中仍会报告 tool_name: "apply_patch"

通用输入字段 外,还会额外提供:

字段 类型 含义
turn_id string Codex 扩展字段。当前激活 turn 的 id。
tool_name string 标准 hook 工具名,例如 Bashapply_patch,或 mcp__fs__read 这类 MCP 名称。
tool_use_id string 本次调用对应的 tool-call id。
tool_input JSON value 工具专属输入。Bashapply_patch 使用 tool_input.command,MCP tools 会发送所有参数。
tool_response JSON value 工具专属输出。对 MCP tools 来说,这是 MCP 调用结果。

写到 stdout 的纯文本会被忽略。

如果向 stdout 输出 JSON,可以使用 systemMessage,并支持下面这个事件专属结构:

{
  "decision": "block",
  "reason": "The Bash output needs review before continuing.",
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "The command updated generated files."
  }
}

其中 additionalContext 会被加入为额外的 开发者上下文。

对这个事件来说,decision: "block" 不会撤销已经完成的 Bash 命令。相反,Codex 会记录这条反馈,用该反馈替换原始工具结果,并从 hook 提供的消息继续驱动模型。

你也可以使用退出码 2,并把反馈原因写到 stderr

如果你想在命令已经执行后,阻止对原始工具结果的正常处理,可以返回 continue: false。Codex 会用你的反馈或停止文本替换原始工具结果,然后从那里继续。

updatedMCPToolOutputsuppressOutput 会被解析,但当前尚未真正支持。Codex 会把这次 hook 运行标记为失败、报告错误,并继续正常处理工具结果。

UserPromptSubmit

matcher 当前对这个事件不起作用。

通用输入字段 外,还会额外提供:

字段 类型 含义
turn_id string Codex 扩展字段。当前激活 turn 的 id。
prompt string 即将发送的用户提示词。

写到 stdout 的纯文本会被加入为额外的开发者上下文。

如果向 stdout 输出 JSON,则支持 通用输出字段 和下面这个事件专属结构:

{
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "Ask for a clearer reproduction before editing files."
  }
}

其中 additionalContext 会被加入为额外的 开发者上下文。

如果你想阻止这条提示词,可返回:

{
  "decision": "block",
  "reason": "Ask for confirmation before doing that."
}

你也可以使用退出码 2,并把阻止原因写到 stderr

Stop

matcher 当前对这个事件不起作用。

通用输入字段 外,还会额外提供:

字段 类型 含义
turn_id string Codex 扩展字段。当前激活 turn 的 id。
stop_hook_active boolean 当前这个 turn 是否已经被 Stop 继续过一次。
last_assistant_message string | null 最新 assistant 消息文本;如果不可用则为 null

Stop 要求在退出码为 0 时向 stdout 输出 JSON。对于这个事件,纯文本输出是无效的。

输出 JSON 时,支持 通用输出字段。如果你想让 Codex 继续运行,可返回:

{
  "decision": "block",
  "reason": "Run one more pass over the failing tests."
}

你也可以使用退出码 2,并把继续执行的原因写到 stderr

对这个事件来说,decision: "block" 并不会拒绝当前 turn 。相反,它会告诉 Codex 继续,并自动创建一条继续执行提示词,把你的 reason 当作新的用户提示词发送下去。

如果有任意一个命中的 Stop hook 返回 continue: false,它会优先于其他 Stop hooks 的 continuation 决策生效。

Schema 定义

如果你需要当前精确的线格式,请查看 Codex GitHub 仓库 中生成的 schema。