概述

Hooks 是什么

你用 Codex 帮你写代码时,它会做很多事:读文件、跑命令、改代码、请求权限、压缩上下文。这些事情发生的时候,你通常只能看着。

Hooks 让你在这些事发生的当中插一段自己的脚本进去。

其实,它是一个事件驱动的扩展机制。Codex 在特定的生命周期节点(比如"用户提交提示词之后"、“调用工具之前”、“会话启动时”)会检查你有没有注册对应的 hook 脚本,有的话就跑一下。你的脚本可以审计、拦截、注入上下文,甚至阻止 Codex 继续执行。

打个比方:AGENTS.md 是你提前写给 Codex 看的说明书,它看了但不一定每句都遵守。Hooks 是你装在流水线上的质检工位,每个工件经过都会被拦下来检查一遍,不合格的直接打回。

Hooks 用来解决什么问题

几个很现实的场景:

你在提示词里不小心粘了一个 API key。如果有个 UserPromptSubmit hook 扫描一下内容,就能在发出去之前拦住你。

Codex 准备跑一条 rm -rf 命令。PreToolUse hook 可以检查命令内容,发现是危险操作就 deny 掉,Codex 会看到错误信息然后换一种方式。

一轮任务跑完了,你想自动跑一遍 lint 检查确保代码风格没问题。Stop hook 可以在 Codex 准备收工时触发一次校验。

上下文太长 Codex 要压缩了,你想在压缩之前先存一份完整日志。PreCompact hook 可以干这个。

简单说:AGENTS.md 和 Rules 管"应该做什么"和"哪些命令能跑",Hooks 管"在某个事件发生的那一刻,跑一段你自己的确定性脚本"。Rules 和 sandbox 是声明式的约束,Hooks 是可编程的干预。

跟 Claude Code Hooks 的区别

两者的设计思路很接近,Claude Code 先做了这套机制,Codex 跟进并做了一些调整。主要差异:

默认开关不同。 Claude Code 的 hooks 默认启用。Codex 的 hooks 在较早版本里默认关闭,需要在 config.toml 里设置 [features].hooks = true 才能用。较新版本已经默认开启了,但如果你发现 hooks 没生效,先检查这个配置。

配置位置不同。 Claude Code 放在 ~/.claude/settings.json 里。Codex 支持两种方式:~/.codex/hooks.json 或者直接在 ~/.codex/config.toml 里用 [[hooks.xxx]] 语法内联写。两个文件里都写了的话 Codex 会合并,但建议只选一种,免得乱。

工具覆盖范围。 这个差异比较大。Claude Code 的 PreToolUse 可以拦截所有工具调用(Bash、Edit、Read、Write、MCP 等)。Codex 的 PreToolUse 目前支持拦截 Bash、apply_patch(文件编辑)和 MCP 工具调用,覆盖面在逐步扩大中。

决策能力。 Claude Code 的 PreToolUse 支持 allow、deny、ask 和 input rewrite(改写工具输入)。Codex 的 PreToolUse 现在也支持 deny(阻止)、allow + updatedInput(允许并改写输入)、以及 additionalContext(追加上下文),能力在逐步对齐。不过 ask(人工确认)这个决策在 Codex 里还没完全落地。

无人值守模式。 这是个 Codex 做得比较好的地方。Claude Code 的 --dangerously-skip-permissions 会连 hooks 一起禁用。Codex 的 --full-auto 模式下 hooks 仍然生效。也就是说你让 Codex 在 CI 里无人值守跑任务时,你的审计和策略检查依然在工作。

超时时间。 Claude Code 的 hook 超时大约 4 秒。Codex 默认 600 秒(够长),你也可以在配置里用 timeout 字段覆盖。

Hooks 分类

Codex 目前有这些 hook 事件。按触发时机排列:

SessionStart:会话启动或恢复时

matcher 过滤的是启动来源(startupresumeclearcompact)。

这个 hook 在新会话开始时跑。典型用途是注入项目上下文。你往 stdout 写的纯文本会被追加为开发者上下文,Codex 会读到。也可以用 JSON 格式的 additionalContext 字段注入。

{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "本项目使用 Python 3.12 + FastAPI,测试用 pytest。"
  }
}

比如你每次开新会话都想提醒 Codex 一些项目约定,又不想写进 AGENTS.md(可能因为这些约定是动态的),就可以用 SessionStart 来注入。

UserPromptSubmit:你提交提示词之后

matcher 对这个事件不起作用(写什么都匹配所有提示词)。

你的提示词已经发出去了但 Codex 还没开始处理时触发。输入里会有 prompt 字段,就是你写的内容。

几个用法:

扫描提示词里有没有敏感信息(API key、密码、内网地址),有就 block 掉。

{
  "decision": "block",
  "reason": "检测到疑似 API key,请移除后再提交。"
}

也可以注入额外上下文。比如你希望每次 Codex 收到关于数据库的问题时都先读某个文档,可以在这个 hook 里判断关键词然后追加 additionalContext

往 stdout 写纯文本也会被当作额外上下文注入。

PreToolUse:Codex 调用工具之前

matcher 过滤的是工具名,比如 Bashapply_patchEditWritemcp__server__tool 这类。

这是用得最多的 hook 之一。Codex 准备调一个工具时,你的脚本会先收到工具名和完整输入(Bash 的话就是完整的命令字符串)。你可以检查、放行、或 deny 掉。

deny 一个 Bash 命令:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "不允许执行 rm -rf,请手动确认。"
  }
}

也可以用退出码 2 + stderr 来阻止,效果一样。

允许但改写输入(比如把 rm -rf 改成 rm -r):

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "updatedInput": {
      "command": "rm -r target_dir"
    }
  }
}

不阻止但追加上下文给模型看:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "additionalContext": "这条命令涉及生成文件目录,请谨慎。"
  }
}

注意:PreToolUse 是护栏,不是铁墙。Codex 通常能通过另一条工具路径做等价操作。比如你 deny 了一条 Bash 的 rm 命令,它可能改用 Python 脚本来删文件。真正的硬限制还是要靠 sandbox 和 approval policy。

PermissionRequest:Codex 发起权限请求时

matcher 过滤的也是工具名。

这个 hook 在 Codex 需要提权或请求审批时触发(比如要执行一条需要确认的命令)。跟 PreToolUse 不同的是,它只在 Codex 主动请求审批时才跑,不需要审批的命令不触发。

你可以直接 allow(不用人确认了)或 deny(直接拒绝):

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

如果多个 hook 都返回了 decision,任意一个 deny 就会生效。都没做决定的话,走正常的人工审批流程。

这个 hook 适合做自动化审批策略:对于特定类型的命令(比如只读的 git 命令),自动放行;对于高危操作(kubectl、aws),直接拒绝。

PostToolUse:工具调用完成之后

matcher 过滤工具名。

工具跑完了(不管成功还是失败)触发。你能拿到工具输入和输出(tool_inputtool_response)。

典型用途是审计和后置检查。比如 Bash 命令跑完之后检查输出里有没有敏感信息泄露,或者文件编辑完之后跑一遍格式化。

注意一个细节:PostToolUse 的 decision: "block" 不会撤销已经执行的操作。它做的是用你的反馈替换原始工具结果,然后让模型基于你的反馈继续。也就是说命令已经跑完了,但模型看到的"结果"变成了你给的反馈。

也可以用 continue: false 直接让 Codex 停在这里。

PreCompact / PostCompact:上下文压缩前后

matcher 过滤的是压缩触发来源:manual(你手动 /compact)或 auto(Codex 自动压缩)。

上下文太长时 Codex 会压缩对话历史。PreCompact 在压缩前跑,PostCompact 在压缩后跑。

PreCompact 的实用场景:压缩前把完整对话存一份到文件,或者发到外部日志系统。返回 continue: false 可以阻止压缩。

PostCompact 的实用场景:压缩后检查摘要质量,或者注入一些压缩时可能丢掉的上下文。

Stop:一轮任务停止时

matcher 不起作用。

Codex 认为一轮任务做完了准备停下来时触发。输入里有 last_assistant_message(AI 最后说的话)和 stop_hook_active(这一轮是否已经被 Stop hook 继续过一次)。

这个 hook 要求 stdout 输出 JSON(纯文本无效)。

返回 decision: "block" 并给个 reason,Codex 不会停下来,而是把你的 reason 当作用户提示词继续跑。这相当于让 Codex “再跑一遍”。

{
  "decision": "block",
  "reason": "请再检查一遍测试覆盖率,确保新增代码都有测试。"
}

stop_hook_active 可以帮你防止死循环:如果已经是 true 了,说明已经继续过一次,这次就让它停吧。

Hooks 怎么配置

配置文件位置

四个常见位置,按优先级从低到高:

  • ~/.codex/hooks.json(用户级)
  • ~/.codex/config.toml 里的 [hooks] 表(用户级)
  • <repo>/.codex/hooks.json(项目级)
  • <repo>/.codex/config.toml 里的 [hooks] 表(项目级)

多个来源的 hooks 会合并加载,不是覆盖。同一层里 hooks.json 和 config.toml 都写了也会合并,但会警告你。建议每层只用一种格式。

项目级 hooks 只在项目被信任时加载。不受信任的项目里,只有用户级和系统级的 hooks 会生效。

hooks.json 格式

hooks.json 的格式一般如下:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.codex/hooks/check_bash.py",
            "timeout": 30,
            "statusMessage": "Checking Bash command"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.codex/hooks/on_stop.py"
          }
        ]
      }
    ]
  }
}

json数据结构是三层:事件名 → matcher 分组 → hook 处理器列表。

matcher 是正则表达式。"Bash" 意思是只匹配 Bash 工具,"Bash|apply_patch" 匹配两个,"*" 或省略 matcher 匹配该事件下所有工具。

timeout 单位秒,省略的话默认 600 秒。

statusMessage 可选,执行时显示在界面上的提示文字。

type 目前只支持 "command""prompt""agent" 类型会被解析但跳过不执行。

config.toml 格式

同样的内容用 TOML 写如下:

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

[[hooks.PreToolUse.hooks]]
type = "command"
command = 'python3 ~/.codex/hooks/check_bash.py'
timeout = 30
statusMessage = "Checking Bash command"

[[hooks.Stop]]

[[hooks.Stop.hooks]]
type = "command"
command = 'python3 ~/.codex/hooks/on_stop.py'

Hook 脚本的输入输出协议

Hook 脚本可以是任何可执行文件(Python、Node.js、Bash + jq 都行),只要遵守一个简单的协议:

输入:Codex 通过 stdin 传一个 JSON 对象给你。里面一定有 session_idcwdhook_event_name 这些通用字段,加上各事件特有的字段(比如 PreToolUse 有 tool_nametool_input)。

输出:往 stdout 写 JSON 来控制行为。退出码 0 + 空输出 = 成功放行。退出码 2 + stderr = 阻止并给出原因。具体的 JSON 格式因事件而异,前面各事件的介绍里都写了。

一个最小的 PreToolUse 检查脚本长这样:

#!/usr/bin/env python3
import json, sys

data = json.load(sys.stdin)
command = data.get("tool_input", {}).get("command", "")

if "rm -rf" in command:
    json.dump({
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "deny",
            "permissionDecisionReason": "rm -rf 被 hook 策略阻止"
        }
    }, sys.stdout)
    sys.exit(0)

# 不输出 = 放行
sys.exit(0)

审核与信任

非托管的 hook(你自己写的或项目里带的)第一次跑之前,Codex 会让你审核。它会给 hook 算一个 hash,你信任之后这个 hash 就记住了。hook 内容改了,hash 变了,需要重新信任。

在 CLI 里用 /hooks 命令可以查看、审核、信任或禁用 hooks。

启动时如果有待审核的 hooks,Codex 会打印警告提醒你。

如果你确实想跳过信任检查(比如在 CI 里),可以传 --dangerously-bypass-hook-trust

关闭 hooks

在 config.toml 里:

[features]
hooks = false

使用建议

先从 PreToolUse 开始。 这是最直接、最容易理解价值的 hook。写一个脚本检查 Bash 命令里有没有危险操作(rm -rfDROP TABLEforce push),deny 掉。十分钟就能搞定,立竿见影。

UserPromptSubmit 适合做安全扫描。 团队用的时候,总会有人不小心把密钥粘进提示词。写个 hook 用正则扫一遍,发现了就 block。成本极低,回报很高。

Stop hook 用来做自动验收。 比如你希望每轮任务结束前都跑一遍测试,可以在 Stop hook 里检查:如果 Codex 改了代码但没跑测试,就 decision: "block" 让它回去跑。配合 stop_hook_active 防止无限循环。

别把所有逻辑都堆在 hooks 里。 Hook 脚本应该是快速、确定性的检查。复杂的逻辑判断、需要大量上下文的分析,留给 AGENTS.md 和 Rules 来做。Hook 适合做"最后一道防线"式的硬检查。

多个 hook 并发启动,不要依赖执行顺序。 同一事件匹配到多个 command hook 时,它们是并发启动的。如果你的 hook A 依赖 hook B 的输出,这个假设不成立。

注意 PreToolUse 的覆盖范围。 它能拦截 Bash、apply_patch 和 MCP 工具。如果你有什么自定义的调用路径不在这三类里,hook 看不到。

仓库级的 hooks 提交到 git 里。 这样团队每个人 clone 下来就自动生效。但第一次用时每个人都要审核信任一次。

超时别设太短。 默认 600 秒很宽裕,但如果你的 hook 要调外部 API(比如查一个策略服务),建议显式设 timeout,比如 30 秒。网络抖动时不至于把整个会话卡住。

总结:本文学习了在CodeX中的Hooks是什么,能帮我们解决哪些问题,以及有哪些hooks供我们使用,怎么去配置他们。学好了之后,我们使用CodeX帮我们写代码就更加游刃有余。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐