本文为非官方中文翻译,内容以 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 在运行前必须经过审查并被信任。
PreToolUse、PermissionRequest、PostToolUse、UserPromptSubmit和Stop在轮次作用域运行。
Codex 在哪里查找 hooks
Codex 会在活动配置层旁边,以以下任一形式发现 hooks:
hooks.jsonconfig.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 事件,例如
PreToolUse、PostToolUse或Stop - 用于决定该事件何时匹配的 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 会运行。prompt和agenthandlers 会被解析,但会被跳过。 - 命令以会话的
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_ROOT和CLAUDE_PLUGIN_DATA。
插件 hooks 使用与其他 hooks 相同的事件 schema。它们属于非托管 hooks,因此在运行前需要进行信任审查。
matcher 模式
matcher 字段是一个 regex 字符串,用于过滤 hooks 何时触发。使用 "*"、"",或者完全省略 matcher,即可匹配受支持事件的每一次发生。
只有部分当前 Codex 事件会应用 matcher:
| Event | matcher 过滤的内容 | 说明 |
|---|---|---|
PermissionRequest | 工具名称 | 支持包括 Bash、apply_patch* 和 MCP 工具名称 |
PostToolUse | 工具名称 | 支持包括 Bash、apply_patch* 和 MCP 工具名称 |
PreToolUse | 工具名称 | 支持包括 Bash、apply_patch* 和 MCP 工具名称 |
SessionStart | 启动来源 | 当前运行时值为 startup、resume 和 clear |
UserPromptSubmit | 不支持 | 为该事件配置的任何 matcher 都会被忽略 |
Stop | 不支持 | 为该事件配置的任何 matcher 都会被忽略 |
*对于 apply_patch,matcher 值也可以使用 Edit 或 Write。
示例:
Bash^apply_patch$Edit|Writemcp__filesystem__read_filemcp__filesystem__.*startup|resume|clear
通用输入字段
每个命令 hook 都会在 stdin 上接收一个 JSON 对象。
这些是你通常会用到的共享字段:
| 字段 | 类型 | 含义 |
|---|---|---|
session_id | string | 当前会话或线程 id。 |
transcript_path | string | null | 会话转录文件的路径(如果有) |
cwd | string | 会话的工作目录 |
hook_event_name | string | 当前 hook 事件名称 |
model | string | Codex 特定扩展。当前激活模型的 slug。 |
作用域为 turn 的 hooks 会在其事件专属表格中将 turn_id 列为 Codex 特定扩展。
SessionStart、PreToolUse、PermissionRequest、PostToolUse、
UserPromptSubmit 和 Stop 还会包含 permission_mode,用于描述当前权限模式,可为
default、acceptEdits、plan、dontAsk 或 bypassPermissions。
transcript_path 为方便起见指向对话转录内容,但转录格式并不是 hooks 的稳定接口,可能会随时间变化。
如果你需要完整的 wire format,请参阅 Schemas。
通用输出字段
SessionStart、UserPromptSubmit 和 Stop 支持以下共享 JSON
字段:
{
"continue": true,
"stopReason": "optional",
"systemMessage": "optional",
"suppressOutput": false
}
| 字段 | 效果 |
|---|---|
continue | 如果为 false,将该 hook 运行标记为已停止 |
stopReason | 记录为停止原因 |
systemMessage | 在 UI 或事件流中显示为警告 |
suppressOutput | 目前会被解析,但尚未实现 |
以退出码 0 且无输出退出会被视为成功,Codex 会继续执行。
PreToolUse 和 PermissionRequest 支持 systemMessage,但 continue、
stopReason 和 suppressOutput 当前不支持这些事件。
如果 PreToolUse hook 返回了这些不受支持的字段之一,Codex 会将该 hook 运行标记为失败、报告错误,并继续该工具调用。
PostToolUse 支持 systemMessage、continue: false 和 stopReason。
suppressOutput 会被解析,但当前该事件尚不支持。
Hooks
SessionStart
此事件中,matcher 应用于 source。
除通用输入字段之外的字段:
| 字段 | 类型 | 含义 |
|---|---|---|
source | string | 会话如何开始:startup、resume 或 clear |
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_patch、Edit 或 Write;但 hook 输入仍会报告 tool_name: "apply_patch"。
除通用输入字段之外的字段:
| 字段 | 类型 | 含义 |
|---|---|---|
turn_id | string | Codex 特定扩展。当前活跃的 Codex turn id |
tool_name | string | 规范的 hook 工具名称,例如 Bash、apply_patch,或类似 mcp__fs__read 的 MCP 名称 |
tool_use_id | string | 此次调用的工具调用 id |
tool_input | JSON value | 工具专属输入。Bash 和 apply_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."
}
}
若要在不阻止的情况下重写一个受支持的工具调用,请返回
带有 updatedInput 的 permissionDecision: "allow":
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"updatedInput": {
"command": "echo rewritten"
}
}
}
对于 Bash 命令和 apply_patch,updatedInput 必须包含一个字符串类型的
command 字段。对于 MCP 工具,updatedInput 是替换参数对象。仅在
permissionDecision: "allow" 时返回 updatedInput;其他
updatedInput 结构会被报告为错误。
permissionDecision: "ask"、旧版 decision: "approve"、continue: false、
stopReason 和 suppressOutput 会被解析,但目前尚不支持。Codex 会将
该 hook 运行标记为失败、报告错误,并继续该工具调用。
PermissionRequest
PermissionRequest 会在 Codex 即将请求批准时运行,例如 shell 提权或托管网络批准。它可以允许该请求、拒绝该请求,或者拒绝做出决定并让正常的批准提示继续进行。它不会针对不需要批准的命令运行。
matcher 应用于 tool_name 和 matcher 别名。当前规范值包括
Bash、apply_patch 和 MCP 工具名称,例如
mcp__server__tool;apply_patch 也会匹配 Edit 和 Write。
除通用输入字段之外的字段:
| 字段 | 类型 | 含义 |
|---|---|---|
turn_id | string | Codex 特定扩展。当前活跃的 Codex turn id |
tool_name | string | 规范的 hook 工具名称,例如 Bash、apply_patch,或类似 mcp__fs__read 的 MCP 名称 |
tool_input | JSON value | 工具专属输入。Bash 和 apply_patch 使用 tool_input.command,而 MCP 工具会发送全部参数。 |
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 返回决策,任何一个 deny 都会胜出。否则,allow
会让请求继续进行而不显示审批提示。如果没有匹配的 hook 作出决策,Codex
会使用正常的审批流程。
不要为 PermissionRequest 返回 updatedInput、updatedPermissions 或
interrupt;这些字段保留给未来行为使用,当前会以失败关闭的方式处理。
PostToolUse
PostToolUse 会在受支持的工具产生输出后运行,包括 Bash、
apply_patch 和 MCP 工具调用。对于 Bash,它也会在以非零状态退出的命令后运行。它无法撤销已经运行的工具所产生的副作用。
这目前还不会拦截所有 shell 调用,只会拦截简单的那些。较新的
unified_exec 机制支持对 shell 的 stdin/stdout 进行更丰富的流式处理,
但拦截仍不完整。类似地,这也不会拦截 WebSearch 或其他非 shell、非
MCP 的工具调用。
matcher 会应用于 tool_name 和 matcher 别名。对于通过
apply_patch 进行的文件编辑,matcher 值可以使用 apply_patch、Edit
或 Write;hook 输入仍然会报告 tool_name: "apply_patch"。
除通用输入字段外的字段:
| Field | Type | Meaning |
|---|---|---|
turn_id | string | Codex 特定扩展。当前活动的 Codex turn id |
tool_name | string | 规范 hook 工具名,例如 Bash、apply_patch,或像 mcp__fs__read 这样的 MCP 名称 |
tool_use_id | string | 此次调用的工具调用 id |
tool_input | JSON value | 工具特定输入。Bash 和 apply_patch 使用 tool_input.command,而 MCP 工具会发送所有参数。 |
tool_response | JSON 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 会用你的反馈或停止文本替换工具结果,并从那里继续。
updatedMCPToolOutput 和 suppressOutput 会被解析,但目前尚不支持。
Codex 会将该 hook 运行标记为失败,报告错误,并继续对工具结果进行正常处理。
UserPromptSubmit
当前此事件不会使用 matcher。
除通用输入字段外的字段:
| Field | Type | Meaning |
|---|---|---|
turn_id | string | Codex 特定扩展。当前活动的 Codex turn id |
prompt | string | 即将发送的用户提示 |
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。
除通用输入字段外的字段:
| Field | Type | Meaning |
|---|---|---|
turn_id | string | Codex 特定扩展。当前活动的 Codex turn id |
stop_hook_active | boolean | 此 turn 是否已由 Stop 继续 |
last_assistant_message | string | null | 最新的 assistant 消息文本(如果可用) |
当 Stop 以 0 退出时,它期望 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。