钩子是 Codex 的一套扩展框架。它允许你把自己的脚本插入智能体循环中,从而实现例如以下能力:
- 把对话发送到自定义日志或分析系统
- 扫描团队提示词,阻止误粘贴 API key
- 自动总结对话,生成持久记忆
- 在回合结束时运行自定义校验器,强制执行团队标准
- 当工作目录匹配特定路径时,动态调整提示词策略
钩子受 config.toml 中的功能开关控制:
[features]
codex_hooks = true需要留意这些运行时行为:
- 来自多个文件的匹配钩子都会运行。
- 对同一事件命中的多个命令型钩子会并发启动,因此一个钩子不能阻止其他已命中的钩子启动。
PreToolUse、PostToolUse、UserPromptSubmit和Stop都是按 turn 作用域运行的。- Hooks 当前在 Windows 上禁用。
Codex 在哪里查找钩子
Codex 会在当前激活配置层旁边查找 hooks.json。
实际使用中,最常见也最有用的两个位置是:
~/.codex/hooks.json<repo>/.codex/hooks.json
如果存在多份 hooks.json,Codex 会加载所有命中的钩子。高优先级配置层不会替换低优先级配置层里的钩子。
配置结构
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"
}
]
}
],
"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的单位是秒。- 也接受
timeoutSec作为别名。 - 如果省略
timeout,Codex 会使用600秒。 statusMessage是可选的。- 命令会以当前会话的
cwd作为工作目录运行。 - 对仓库级钩子,优先使用基于 Git 根目录解析的路径,而不是
.codex/hooks/...这类相对路径。Codex 可能从子目录启动,基于 Git 根目录的写法更稳定。
匹配器模式
matcher 字段是一个正则表达式字符串,用来过滤 hook 何时触发。使用 "*"、"",或完全省略 matcher,都表示匹配该事件的所有支持触发。
当前只有部分 Codex 事件真正会使用 matcher:
| 事件 | matcher 过滤的对象 |
说明 |
|---|---|---|
PostToolUse |
工具名 | 当前运行时只会发出 Bash。 |
PreToolUse |
工具名 | 当前运行时只会发出 Bash。 |
SessionStart |
启动来源 | 当前运行时的值只有 startup 和 resume。 |
UserPromptSubmit |
不支持 | 该事件中配置的 matcher 会被忽略。 |
Stop |
不支持 | 该事件中配置的 matcher 会被忽略。 |
示例:
Bashstartup|resumeEdit|Write
最后这个例子在正则语法上是有效的,但当前 Codex 的 PreToolUse 和 PostToolUse 事件只会发出 Bash,所以今天它实际上不会匹配到任何内容。
通用输入字段
每个命令型钩子都会通过 stdin 收到一个 JSON 对象。
这些共享字段通常最常用:
| 字段 | 类型 | 含义 |
|---|---|---|
session_id |
string |
当前 session 或 thread id。 |
transcript_path |
string | null |
session transcript 文件路径;如果不存在则为 null。 |
cwd |
string |
当前会话的工作目录。 |
hook_event_name |
string |
当前 hook 事件名。 |
model |
string |
当前激活模型的 slug。 |
按 turn 作用域运行的 hooks 会在各自的事件专属字段表里额外列出 turn_id。
如果你需要完整的当前线格式,请参见 Schema 定义。
通用输出字段
SessionStart、UserPromptSubmit 和 Stop 支持以下共享 JSON 字段:
{
"continue": true,
"stopReason": "optional",
"systemMessage": "optional",
"suppressOutput": false
}| 字段 | 作用 |
|---|---|
continue |
若为 false,表示该次 hook 运行被标记为停止。 |
stopReason |
记录为停止原因。 |
systemMessage |
作为警告显示在界面或事件流中。 |
suppressOutput |
当前会被解析,但尚未真正实现。 |
退出码为 0 且没有任何输出,会被视为成功,Codex 会继续执行。
PreToolUse 支持 systemMessage,但当前不支持 continue、stopReason 和 suppressOutput。
PostToolUse 支持 systemMessage、continue: false 和 stopReason。suppressOutput 虽然会被解析,但当前仍未真正支持。
Hooks
SessionStart
这个事件中的 matcher 会作用在 source 上。
除 通用输入字段 外,还会额外提供:
| 字段 | 类型 | 含义 |
|---|---|---|
source |
string |
会话启动方式:startup 或 resume。 |
写到 stdout 的纯文本会被追加为额外的 开发者上下文。
如果向 stdout 输出 JSON,则支持 通用输出字段,以及下面这个该事件专属结构:
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Load the workspace conventions before editing."
}
}其中 additionalContext 会被加入为额外的 开发者上下文。
PreToolUse
当前 PreToolUse 只支持拦截 Bash 工具。模型仍然可能通过先把脚本写到磁盘、再让 Bash 执行这个脚本的方式绕过它,因此应把它视为一个实用护栏,而不是绝对的强制边界。
matcher 会作用在 tool_name 上,而当前这个值始终是 Bash。
除 通用输入字段 外,还会额外提供:
| 字段 | 类型 | 含义 |
|---|---|---|
turn_id |
string |
Codex 扩展字段。当前激活 turn 的 id。 |
tool_name |
string |
当前始终是 Bash。 |
tool_use_id |
string |
本次调用对应的 tool-call id。 |
tool_input.command |
string |
Codex 即将执行的 shell 命令。 |
写到 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。
permissionDecision: "allow" 和 "ask"、旧版 decision: "approve"、updatedInput、additionalContext、continue: false、stopReason 和 suppressOutput 虽然会被解析,但目前尚未支持,因此会按开放失败处理。
PostToolUse
当前 PostToolUse 只支持 Bash 工具结果。它并不限于退出成功的命令:只要 Codex 发出了 Bash 后置工具负载,非交互 exec_command 的结果同样可能触发 PostToolUse。但它无法撤销那条已经执行过的命令所产生的副作用。
matcher 会作用在 tool_name 上,而当前该值始终是 Bash。
除 通用输入字段 外,还会额外提供:
| 字段 | 类型 | 含义 |
|---|---|---|
turn_id |
string |
Codex 扩展字段。当前激活 turn 的 id。 |
tool_name |
string |
当前始终是 Bash。 |
tool_use_id |
string |
本次调用对应的 tool-call id。 |
tool_input.command |
string |
Codex 刚刚执行过的 shell 命令。 |
tool_response |
JSON value |
Bash 工具输出负载;当前通常是一个 JSON 字符串。 |
写到 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 会被解析,但当前尚未真正支持,因此仍按开放失败处理。
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 继续,并自动创建一条 continuation prompt,把你的 reason 当作新的用户提示词发送下去。
如果有任意一个命中的 Stop hook 返回 continue: false,它会优先于其他 Stop hooks 的 continuation 决策生效。
Schema 定义
如果你需要当前精确的线格式,请查看 Codex GitHub 仓库 中生成的 schema。