跳到主要内容

本文为非官方中文翻译,内容以 OpenAI 官方英文文档为准。
官方来源:https://developers.openai.com/codex/hooks

Hooks

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

Hooks 是 Codex 的一个可扩展框架。它允许你将自己的脚本注入到 agentic loop 中,从而实现如下功能:

  • 将对话发送到自定义日志/分析引擎
  • 扫描你团队的 prompts,以阻止意外粘贴 API keys
  • 总结对话,以自动创建持久记忆
  • 在一次对话轮次停止时运行自定义校验检查,以强制执行标准
  • 在特定目录中自定义 prompting

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

[features]
hooks = false

使用 hooks 作为规范的功能键。codex_hooks 仍然可用,但已作为弃用别名。

管理员也可以在 requirements.toml 中用相同方式强制关闭 hooks,即设置 [features].hooks = false

需要注意的运行时行为:

  • 来自多个文件的匹配 hooks 都会运行。
  • 对于同一事件的多个匹配 command hook,会并发启动,因此一个 hook 无法阻止另一个匹配 hook 启动。
  • 非托管的 command hook 在运行前必须经过审查并被信任。
  • PreToolUsePermissionRequestPostToolUseUserPromptSubmitStop 在轮次作用域运行。

Codex 在哪里查找 hooks

Codex 会在活动配置层旁边,以以下任一形式发现 hooks:

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

已安装的插件也可以通过其插件清单或默认的 hooks/hooks.json 文件捆绑生命周期配置。有关插件打包规则,请参阅构建插件

在实践中,最有用的四个位置是:

  • ~/.codex/hooks.json
  • ~/.codex/config.toml
  • /.codex/hooks.json
  • /.codex/config.toml

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

本版本中,插件 hooks 默认关闭。如果 [features].plugin_hooks = true,Codex 还可以发现随已启用插件捆绑的 hooks。 否则,已启用的插件不会运行其捆绑的 hooks。

只有当项目 .codex/ 层被信任时,项目本地 hooks 才会加载。 在不受信任的项目中,Codex 仍会从各自活动的配置层加载用户和系统 hooks。

审查和管理 hooks

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

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

配置结构

Hooks 按三个层级组织:

  • hook 事件,例如 PreToolUsePostToolUseStop
  • 用于决定该事件何时匹配的 matcher group
  • 当 matcher group 匹配时运行的一个或多个 hook handler
{
"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 的 handlers。
  • 当前只有 type: "command" 的 handlers 会运行。promptagent handlers 会被解析,但会被跳过。
  • 命令以会话的 cwd 作为其工作目录运行。
  • 对于仓库本地 hooks,建议从 git 根目录解析路径,而不是使用如 .codex/hooks/... 这样的相对路径。Codex 可能从某个子目录启动,而基于 git 根目录的路径可以保持 hook 位置稳定。

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 的托管 hooks

企业托管 requirements 也可以在 [hooks] 下内联定义 hooks。 当管理员希望强制执行 hook 配置,同时通过 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"

托管 hooks 说明:

  • managed_dir 用于 macOS 和 Linux。
  • windows_managed_dir 用于 Windows。
  • Codex 不会分发 managed_dir 中的脚本;你的企业工具链必须单独安装并更新它们。
  • 托管 hook 命令应使用配置的托管目录下的脚本绝对路径。

插件捆绑 hooks

在本版本中,插件捆绑 hooks 采用 opt-in 方式。启用 [features].plugin_hooks = true 且某个插件已启用时,Codex 可以从该插件加载生命周期 hooks,并与用户、项目和托管 hooks 一同使用。

[features]
plugin_hooks = true

默认情况下,Codex 会在插件根目录内查找 hooks/hooks.json。插件清单可以通过 .codex-plugin/plugin.json 中的 hooks 条目覆盖该默认值。清单条目可以是一个以 ./ 开头的路径、一个以 ./ 开头的路径数组、一个内联 hooks 对象,或一个内联 hooks 对象数组。

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

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

插件 hook 命令会收到以下环境变量:

  • PLUGIN_ROOT 是 Codex 特有的扩展,指向已安装的插件根目录。
  • PLUGIN_DATA 是 Codex 特有的扩展,指向插件的可写数据目录。
  • 出于与现有插件 hooks 的兼容性,Codex 还会设置 CLAUDE_PLUGIN_ROOTCLAUDE_PLUGIN_DATA

插件 hooks 使用与其他 hooks 相同的事件 schema。它们属于非托管 hooks,因此在运行前需要进行信任审查。

matcher 模式

matcher 字段是一个 regex 字符串,用于过滤 hooks 何时触发。使用 "*""",或者完全省略 matcher,即可匹配受支持事件的每一次发生。

只有部分当前 Codex 事件会应用 matcher

Eventmatcher 过滤的内容说明
PermissionRequest工具名称支持包括 Bashapply_patch* 和 MCP 工具名称
PostToolUse工具名称支持包括 Bashapply_patch* 和 MCP 工具名称
PreToolUse工具名称支持包括 Bashapply_patch* 和 MCP 工具名称
SessionStart启动来源当前运行时值为 startupresumeclear
UserPromptSubmit不支持为该事件配置的任何 matcher 都会被忽略
Stop不支持为该事件配置的任何 matcher 都会被忽略

*对于 apply_patchmatcher 值也可以使用 EditWrite

示例:

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

通用输入字段

每个命令 hook 都会在 stdin 上接收一个 JSON 对象。

这些是你通常会用到的共享字段:

字段类型含义
session_idstring当前会话或线程 id。
transcript_pathstring | null会话转录文件的路径(如果有)
cwdstring会话的工作目录
hook_event_namestring当前 hook 事件名称
modelstringCodex 特定扩展。当前激活模型的 slug。

作用域为 turn 的 hooks 会在其事件专属表格中将 turn_id 列为 Codex 特定扩展。

SessionStartPreToolUsePermissionRequestPostToolUseUserPromptSubmitStop 还会包含 permission_mode,用于描述当前权限模式,可为 defaultacceptEditsplandontAskbypassPermissions

transcript_path 为方便起见指向对话转录内容,但转录格式并不是 hooks 的稳定接口,可能会随时间变化。

如果你需要完整的 wire format,请参阅 Schemas

通用输出字段

SessionStartUserPromptSubmitStop 支持以下共享 JSON 字段:

{
"continue": true,
"stopReason": "optional",
"systemMessage": "optional",
"suppressOutput": false
}
字段效果
continue如果为 false,将该 hook 运行标记为已停止
stopReason记录为停止原因
systemMessage在 UI 或事件流中显示为警告
suppressOutput目前会被解析,但尚未实现

以退出码 0 且无输出退出会被视为成功,Codex 会继续执行。

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

PostToolUse 支持 systemMessagecontinue: falsestopReasonsuppressOutput 会被解析,但当前该事件尚不支持。

Hooks

SessionStart

此事件中,matcher 应用于 source

通用输入字段之外的字段:

字段类型含义
sourcestring会话如何开始:startupresumeclear

stdout 上的纯文本会作为额外的开发者上下文添加。

stdout 上的 JSON 支持通用输出字段以及此 hook 专属结构:

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

additionalContext 文本会作为额外的开发者上下文添加。

PreToolUse

PreToolUse 可以拦截 Bash、通过 apply_patch 执行的文件编辑,以及 MCP 工具调用。它仍然更像是一种防护措施,而不是完整的强制执行边界,因为 Codex 通常可以通过其他受支持的工具路径执行等效操作。

这目前还不会拦截所有 shell 调用,只会拦截简单的那些。较新的 unified_exec 机制允许对 shell 的 stdin/stdout 进行更丰富的流式处理, 但拦截仍不完整。类似地,这也不会拦截 WebSearch 或其他非 shell、非 MCP 工具调用。

matcher 应用于 tool_name 和 matcher 别名。对于通过 apply_patch 进行的文件编辑,matcher 值可以使用 apply_patchEditWrite;但 hook 输入仍会报告 tool_name: "apply_patch"

通用输入字段之外的字段:

字段类型含义
turn_idstringCodex 特定扩展。当前活跃的 Codex turn id
tool_namestring规范的 hook 工具名称,例如 Bashapply_patch,或类似 mcp__fs__read 的 MCP 名称
tool_use_idstring此次调用的工具调用 id
tool_inputJSON value工具专属输入。Bashapply_patch 使用 tool_input.command,而 MCP 工具会发送全部参数。

stdout 上的纯文本会被忽略。

stdout 上的 JSON 可以使用 systemMessage。要拒绝一个受支持的工具调用,请返回此 hook 专属结构:

{
"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."
}
}

若要在不阻止的情况下重写一个受支持的工具调用,请返回 带有 updatedInputpermissionDecision: "allow"

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

对于 Bash 命令和 apply_patchupdatedInput 必须包含一个字符串类型的 command 字段。对于 MCP 工具,updatedInput 是替换参数对象。仅在 permissionDecision: "allow" 时返回 updatedInput;其他 updatedInput 结构会被报告为错误。

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

PermissionRequest

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

matcher 应用于 tool_name 和 matcher 别名。当前规范值包括 Bashapply_patch 和 MCP 工具名称,例如 mcp__server__toolapply_patch 也会匹配 EditWrite

通用输入字段之外的字段:

字段类型含义
turn_idstringCodex 特定扩展。当前活跃的 Codex turn id
tool_namestring规范的 hook 工具名称,例如 Bashapply_patch,或类似 mcp__fs__read 的 MCP 名称
tool_inputJSON value工具专属输入。Bashapply_patch 使用 tool_input.command,而 MCP 工具会发送全部参数。
tool_input.descriptionstring | null人类可读的批准原因,当 Codex 提供该信息时

stdout 上的纯文本会被忽略。

某些工具输入可能包含人类可读描述,但不要依赖每个工具都提供 tool_input.description 字段。

若要批准该请求,请返回:

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

若要拒绝该请求,请返回:

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

如果多个匹配的 hook 返回决策,任何一个 deny 都会胜出。否则,allow 会让请求继续进行而不显示审批提示。如果没有匹配的 hook 作出决策,Codex 会使用正常的审批流程。

不要为 PermissionRequest 返回 updatedInputupdatedPermissionsinterrupt;这些字段保留给未来行为使用,当前会以失败关闭的方式处理。

PostToolUse

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

这目前还不会拦截所有 shell 调用,只会拦截简单的那些。较新的 unified_exec 机制支持对 shell 的 stdin/stdout 进行更丰富的流式处理, 但拦截仍不完整。类似地,这也不会拦截 WebSearch 或其他非 shell、非 MCP 的工具调用。

matcher 会应用于 tool_name 和 matcher 别名。对于通过 apply_patch 进行的文件编辑,matcher 值可以使用 apply_patchEditWrite;hook 输入仍然会报告 tool_name: "apply_patch"

通用输入字段外的字段:

FieldTypeMeaning
turn_idstringCodex 特定扩展。当前活动的 Codex turn id
tool_namestring规范 hook 工具名,例如 Bashapply_patch,或像 mcp__fs__read 这样的 MCP 名称
tool_use_idstring此次调用的工具调用 id
tool_inputJSON value工具特定输入。Bashapply_patch 使用 tool_input.command,而 MCP 工具会发送所有参数。
tool_responseJSON value工具特定输出。对于 MCP 工具,这是 MCP 调用结果。

stdout 上的纯文本会被忽略。

stdout 上的 JSON 可以使用 systemMessage 以及此 hook 特定的结构:

{
"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

通用输入字段外的字段:

FieldTypeMeaning
turn_idstringCodex 特定扩展。当前活动的 Codex turn id
promptstring即将发送的用户提示

stdout 上的纯文本会作为额外的开发者上下文被添加。

stdout 上的 JSON 支持通用输出字段以及 此 hook 特定的结构:

{
"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

通用输入字段外的字段:

FieldTypeMeaning
turn_idstringCodex 特定扩展。当前活动的 Codex turn id
stop_hook_activeboolean此 turn 是否已由 Stop 继续
last_assistant_messagestring | null最新的 assistant 消息文本(如果可用)

Stop0 退出时,它期望 stdout 上输出 JSON。纯文本输出对此事件无效。

stdout 上的 JSON 支持通用输出字段。要让 Codex 继续,返回:

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

你也可以使用退出码 2,并将继续原因写入 stderr

对于此事件,decision: "block" 不会拒绝该 turn。相反,它会告诉 Codex 继续,并自动创建一个新的 continuation prompt 作为新的用户提示, 使用你的 reason 作为该提示文本。

如果任何匹配的 Stop hook 返回 continue: false,它会优先于其他匹配的 Stop hook 的继续决策。

Schemas

链接的 main 分支 schema 可能包含当前发布版本中不存在的 hook 字段。 请以本页作为发布行为的参考。

如果你需要精确的当前 wire format,请参阅 Codex GitHub repository 中的生成 schema。