钩子是 Codex 的一套扩展框架。它允许你把自己的脚本插入智能体循环中,从而实现例如以下能力:
- 把对话发送到自定义日志或分析系统
- 扫描团队提示词,阻止误粘贴 API key
- 自动总结对话,生成持久记忆
- 在 turn 结束时运行自定义校验检查,强制执行团队标准
- 当工作目录匹配特定路径时,动态调整提示词策略
Hooks 默认启用。如果需要在 config.toml 中关闭,请设置:
[features]
hooks = false请把 hooks 作为标准功能键。codex_hooks 仍可作为已弃用 alias 使用。
管理员也可以在 requirements.toml 中用同样方式强制关闭 hooks:[features].hooks = false。
需要留意这些运行时行为:
- 来自多个文件的匹配钩子都会运行。
- 对同一事件命中的多个命令型钩子会并发启动,因此一个钩子不能阻止其他已命中的钩子启动。
- 非托管命令型 hooks 必须经过审核并被信任后才会运行。
PreToolUse、PermissionRequest、PostToolUse、UserPromptSubmit和Stop都是按 turn 作用域运行的。
Codex 在哪里查找钩子
Codex 会在当前激活配置层旁边查找以下任一形式的钩子配置:
hooks.jsonconfig.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 分成三层:
- 事件名,例如
PreToolUse、PostToolUse或Stop - 决定该事件何时命中的 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"的处理器会运行。prompt和agent处理器会被解析,但会被跳过。 - 命令会以当前会话的
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_ROOT和CLAUDE_PLUGIN_DATA。
Plugin hooks 使用和其他 hooks 相同的事件 schema。它们属于非托管 hooks,因此运行前需要经过信任审核。
匹配器模式
matcher 字段是一个正则表达式字符串,用来过滤 hook 何时触发。使用 "*"、"",或完全省略 matcher,都表示匹配该事件的所有支持触发。
当前只有部分 Codex 事件真正会使用 matcher:
| 事件 | matcher 过滤的对象 |
说明 |
|---|---|---|
PermissionRequest |
工具名 | 支持 Bash、apply_patch* 和 MCP tool names。 |
PostToolUse |
工具名 | 支持 Bash、apply_patch* 和 MCP tool names。 |
PreToolUse |
工具名 | 支持 Bash、apply_patch* 和 MCP tool names。 |
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
通用输入字段
每个命令型钩子都会通过 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。
SessionStart、PreToolUse、PermissionRequest、PostToolUse、UserPromptSubmit 和 Stop 也会包含 permission_mode,表示当前权限模式,值可能是 default、acceptEdits、plan、dontAsk 或 bypassPermissions。
transcript_path 只是为了方便指向对话 transcript;transcript 格式不是 hooks 的稳定接口,未来可能变化。
如果你需要完整的当前线格式,请参见 Schema 定义。
通用输出字段
SessionStart、UserPromptSubmit 和 Stop 支持以下共享 JSON 字段:
{
"continue": true,
"stopReason": "optional",
"systemMessage": "optional",
"suppressOutput": false
}| 字段 | 作用 |
|---|---|
continue |
若为 false,表示该次 hook 运行被标记为停止。 |
stopReason |
记录为停止原因。 |
systemMessage |
作为警告显示在界面或事件流中。 |
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,则支持 通用输出字段,以及下面这个该事件专属结构:
{
"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_patch、Edit 或 Write;hook input 中仍会报告 tool_name: "apply_patch"。
除 通用输入字段 外,还会额外提供:
| 字段 | 类型 | 含义 |
|---|---|---|
turn_id |
string |
Codex 扩展字段。当前激活 turn 的 id。 |
tool_name |
string |
标准 hook 工具名,例如 Bash、apply_patch,或 mcp__fs__read 这类 MCP 名称。 |
tool_use_id |
string |
本次调用对应的 tool-call id。 |
tool_input |
JSON value |
工具专属输入。Bash 和 apply_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_patch,updatedInput 必须包含字符串类型的 command 字段。对于 MCP tools,updatedInput 是替换后的参数对象。只应在 permissionDecision: "allow" 时返回 updatedInput;其他 updatedInput 形态会被报告为错误。
permissionDecision: "ask"、旧版 decision: "approve"、continue: false、stopReason 和 suppressOutput 虽然会被解析,但目前尚未支持。Codex 会把这次 hook 运行标记为失败、报告错误,并继续执行工具调用。
PermissionRequest
PermissionRequest 会在 Codex 即将请求审批时运行,例如 shell 提权或托管网络审批。它可以允许请求、拒绝请求,或者不做决定并让常规审批提示继续显示。它不会在不需要审批的命令上运行。
matcher 会作用在 tool_name 和 matcher aliases 上。当前标准值包括 Bash、apply_patch,以及 mcp__server__tool 这类 MCP tool names;apply_patch 也会匹配 Edit 和 Write。
除 通用输入字段 外,还会额外提供:
| 字段 | 类型 | 含义 |
|---|---|---|
turn_id |
string |
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 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 返回 updatedInput、updatedPermissions 或 interrupt;这些字段预留给未来行为,目前会按关闭失败处理。
PostToolUse
PostToolUse 会在受支持工具产生输出后运行,包括 Bash、apply_patch 和 MCP tool calls。对 Bash 来说,命令以非零状态退出后也会运行。它无法撤销已经执行过的工具副作用。
matcher 会作用在 tool_name 和 matcher aliases 上。对于通过 apply_patch 完成的文件编辑,matcher 值可以使用 apply_patch、Edit 或 Write;hook input 中仍会报告 tool_name: "apply_patch"。
除 通用输入字段 外,还会额外提供:
| 字段 | 类型 | 含义 |
|---|---|---|
turn_id |
string |
Codex 扩展字段。当前激活 turn 的 id。 |
tool_name |
string |
标准 hook 工具名,例如 Bash、apply_patch,或 mcp__fs__read 这类 MCP 名称。 |
tool_use_id |
string |
本次调用对应的 tool-call id。 |
tool_input |
JSON value |
工具专属输入。Bash 和 apply_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 会用你的反馈或停止文本替换原始工具结果,然后从那里继续。
updatedMCPToolOutput 和 suppressOutput 会被解析,但当前尚未真正支持。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。