Codex Hooks使用实践

Codex Hooks使用实践

一、为什么要了解 Hooks?

修复 bug 时,先写出会失败的测试,确认测试失败,提交这个测试文件;再让 Agent 修复代码,并且禁止 Agent 修改这份测试。同时配套钩子做强制防护。

这是《The AI-Native SDLC Playbook------How to transform your software development lifecycle with AI-stage by stage》中的一段话。 可见要用好 AI 编程就绕不开Hooks(钩子)。

生命周期钩子(Lifecycle Hooks)可以让 AI 在执行任务的特定阶段,自动运行脚本或调用 MCP 工具。我平时用Codex比较多,因此就拿Codex举例。

例如:

  • Codex 准备执行 Shell 命令时,自动检查有没有危险操作。

  • Codex 修改完代码后,自动运行 ESLint、Prettier 或其他检查工具。

  • Codex 完成一轮任务时,检查测试是否通过、是否遗漏必要操作。

  • Codex 启动新会话时,自动加载项目上下文。

  • 子 Agent 启动或结束时,自动执行日志记录、上下文注入或质量检查。

它最重要的价值是:将一些本来依赖 AI 自觉遵守的开发规范,变成确定性执行的自动化流程。

举个例子,要求 Codex:

修改登录模块,修复一个 Bug,并确保单元测试通过。

如果配置了 Hooks,执行过程可以是:

  1. UserPromptSubmit:检查用户输入,必要时添加项目约束。
  2. PreToolUse:Codex 即将执行命令,检查是否包含危险操作。
  3. Codex 修改代码或运行工具。
  4. PostToolUse:自动检测刚修改的文件是否违反规范。
  5. Codex 继续修复和验证。
  6. Stop:检查本轮任务的完成条件,必要时提示 Codex 继续。

二、如何配置 Hooks?

Codex 支持全局和项目级配置。

复制代码
C:\
└── Users\
    └── <username>\
        └── .codex\
            ├── config.toml
            ├── hooks.json          # 全局 Hooks
            └── hooks\
                └── ...

D:\
└── Workbench\
    └── my-project\
        ├── AGENTS.md
        ├── src\
        └── .codex\
            ├── config.toml
            ├── hooks.json          # 项目 Hooks
            └── hooks\
                ├── pre_tool.py
                ├── post_tool.py
                └── stop.py

这两种范围适合不同的需求:

配置位置 作用范围 适合
%USERPROFILE%\.codex\hooks.json 当前用户的多个 Codex 项目 通用安全策略、通知、日志
<项目根目录>\.codex\hooks.json 当前项目 自动测试、格式检查、项目规范

Codex 会加载多个匹配的 Hook 来源,而不是简单地让项目配置覆盖全局配置。

因此,全局 Hooks 应尽量少,项目级 Hooks 应根据技术栈定制。

另外,当前 Codex 需要用户审核并信任非托管 Hooks。比如下图就是我配置Hooks后Chatgpt Desktop在对话框下方展示的钩子图标上标注了一个小黄点提示我需要审核。

点击钩子图标就可以进行审批操作。

对于 CLI,可以通过 /hooks 查看、授权或禁用。

三、使用实践举例

3.1 Bug 修复的实践

以实践 AI-Native SDLC Playbook 中提到的"修 bug 先写失败测试,再让 agent 修复代码(不准改测试内容)"就需要使用钩子做强制防护。

  1. 首先是在项目的.codex目录中增加hooks.json文件,内容可以自己手写也可以让Codex自己编写。目的就是缺陷修复时进行测试防护。示例中的hooks.json文件内容如下(commandWindows中使用的代码可以是其他语言,比如python):
json 复制代码
{
  "description": "ContentFerry bug-fix test protection",
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "^(?:apply_patch|Bash|mcp__.*|Write|Edit|Delete|Move|Rename|Create|Update|Save|Append|Remove|Replace|Truncate|Overwrite|Patch)$",
        "hooks": [
          {
            "type": "command",
            "command": "node \"$(git rev-parse --show-toplevel)/.codex/hooks/verify-fix-test-immutability.mjs\" --hook",
            "commandWindows": "node (Join-Path (git rev-parse --show-toplevel) '.codex/hooks/verify-fix-test-immutability.mjs') --hook",
            "statusMessage": "Checking protected test files"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "^(?:apply_patch|Bash|mcp__.*|Write|Edit|Delete|Move|Rename|Create|Update|Save|Append|Remove|Replace|Truncate|Overwrite|Patch)$",
        "hooks": [
          {
            "type": "command",
            "command": "node \"$(git rev-parse --show-toplevel)/.codex/hooks/verify-fix-test-immutability.mjs\" --post-hook",
            "commandWindows": "node (Join-Path (git rev-parse --show-toplevel) '.codex/hooks/verify-fix-test-immutability.mjs') --post-hook",
            "statusMessage": "Verifying protected test files"
          }
        ]
      }
    ]
  }
}

这个示例只是基础演示。正则表达式并不能完整识别 PowerShell、CMD、命令包装或经过编码的危险操作,不能替代沙箱及权限控制。

  1. 当遇到bug时,先复现bug,然后写回归测试,并确保这个测试是因为这个bug才失败的。
  2. 提交测试文件,并启动保护:npm run fix:begin -- HEAD ("fix:begin" 对应我在package.json中设置的 "node .codex/hooks/verify-fix-test-immutability.mjs --begin")

以下是verify-fix-test-immutability.mjs中开启保护的片段,具体内容可以让codex根据"修 bug 先写失败测试,再让 agent 修复代码(不准改测试内容)"这样的要求进行编写

javascript 复制代码
function beginProtection(testCommit) {
  validateTestCommit(testCommit);
  const changedSinceTestCommit = changedPaths(["diff", "--name-only", testCommit, "--"]);
  const untrackedPaths = changedPaths(["ls-files", "--others", "--exclude-standard"]);
  const existingTestChanges = findProtectedTestChanges(changedSinceTestCommit, untrackedPaths);
  if (existingTestChanges.length > 0) {
    throw new Error(`开启保护前工作区已存在测试文件改动:${existingTestChanges.join("、")}`);
  }
  const markerPath = runGit(["rev-parse", "--path-format=absolute", "--git-path", TEST_COMMIT_MARKER_NAME]).stdout;
  writeFileSync(markerPath, `${testCommit}\n`, "utf8");
  if (getConfiguredTestCommit() !== testCommit) {
    throw new Error("测试保护标记写入后无法回读,未能开启测试保护。");
  }
  console.log(`已开启测试保护,基准提交为 ${testCommit}。Codex hook 将拦截已识别的测试文件写入;结束前请运行 npm run fix:verify。`);
}
  1. 让Agent修复bug。hook 会拦截已识别的测试文件写入。完成后再运行针对性测试和该改动要求的其他检查。

以下是我故意让codex修改测试文件的示例,修改过程中被hook拦截了

  1. 运行 npm run fix:verify,确认基线之后没有测试文件变化。随后运行 npm run fix:end,再 review 最终差异。
javascript 复制代码
function endProtection() {
  const testCommit = getConfiguredTestCommit();
  if (testCommit) verifyTestCommit(testCommit);
  const markerPath = runGit(["rev-parse", "--path-format=absolute", "--git-path", TEST_COMMIT_MARKER_NAME]).stdout;
  try {
    unlinkSync(markerPath);
  } catch (error) {
    if (error instanceof Error && "code" in error && error.code === "ENOENT") {
      console.log("本地测试保护当前未开启。");
      return;
    }
    throw error;
  }
  console.log("已关闭本地测试保护。");
}

3.2 其他注意事项

不要在每次 PostToolUse 触发后,立即对整个项目运行所有测试。

因为 Codex 可能连续修改十几个文件,每改一次就全量测试,很容易导致运行缓慢甚至资源竞争。

更好的策略是:

  • PostToolUse:记录被修改的文件,或进行轻量检查。

  • Stop:执行一次针对性的 lint、typecheck 和测试。

  • CI:执行全量测试及完整质量检查。

四、Codex Hooks 支持哪些事件?

目前官方文档列出的主要事件如下。

Hook 事件 触发时机 推荐用途
SessionStart 新建(startup)、恢复(resume)、清空重建(clear)、压缩(compact)、分叉(fork) 加载或恢复项目上下文
UserPromptSubmit 用户提交消息 输入校验、动态上下文
PreToolUse 工具执行前 风险拦截、参数检查
PermissionRequest Codex 请求权限时 审批策略
PostToolUse 工具执行后 检查结果、代码规范
PreCompact 上下文压缩前 保存关键任务状态
PostCompact 上下文压缩后 恢复任务上下文
SubagentStart 子 Agent 启动 注入角色规范
SubagentStop 子 Agent 完成 收集结果、质量反馈
Stop Codex 本轮响应即将结束 检查任务完成情况
Interrupt 用户中断正在执行的任务 清理、记录
SessionEnd 主会话结束 日志、状态持久化

其中,我最建议优先掌握的是 PreToolUse、PostToolUse、Stop、SessionStart。

它们分别解决执行安全、开发质量、任务完整性和上下文管理这四类常见问题。

4.1 实际项目推荐的目录结构

对于需要长期维护、使用多 Agent 的开发项目,我会推荐:

复制代码
my-project/
├── AGENTS.md
├── .codex/
│   ├── config.toml
│   ├── hooks.json
│   │
│   └── hooks/
│       ├── pre_tool.py
│       ├── post_tool.py
│       ├── stop.py
│       ├── session_start.py
│       └── subagent_stop.py
│
├── scripts/
│   ├── lint.py
│   ├── test.py
│   └── validate.py
│
└── src/

我的建议是从最小配置开始,逐步增加 Hook,而不是一次启用全部生命周期事件。

尤其注意以下工程原则:

  1. 快速执行:高频 Hook 尽量在数百毫秒内完成,避免每次工具调用都产生明显延迟。
  2. 幂等设计:同一个事件可能重复出现,Hook 不能因为重复执行而破坏项目状态。
  3. 明确超时:不同事件使用不同超时,避免脚本卡住整个工作流。
  4. 减少副作用:检查类 Hook 应尽量只读;自动格式化和修改文件需要防止反复触发。
  5. 控制输出:仅输出关键错误和必要的上下文,避免消耗大量模型 token。
  6. 不要依赖 Hook 作为唯一安全边界:高危行为还应通过 Codex 权限、操作系统及 CI 限制。
  7. 每次修改 Hook 都重新验证:尤其要验证脚本直接执行成功和 Codex 生命周期实际触发是两回事。

五、hooks.json、AGENTS.md、Skills、Rules 应该如何分工?

Codex 提供多种扩展机制,但每一种都适合不同的职责。

机制 核心作用 典型例子
AGENTS.md 告诉 AI 应遵守什么规范 代码风格、架构约束
Skills 教 AI 如何完成某类任务 代码 Review、生成 PRD
Hooks 在指定时机自动执行程序 自动测试、校验、拦截
Rules 约束命令执行与权限行为 禁止或限制特定命令
MCP 连接外部工具和服务 GitHub、数据库、浏览器
相关推荐
LEE1 小时前
为什么你的 Claude Code 还在转圈等,别人的已经干完三件事了?
前端·后端·ai编程
Layer2 小时前
一些脱离 Mac 依赖的 Agent 操作 iPhone 实现
ios·ai编程·测试
黑黑的脸蛋3 小时前
同时用 Claude Code 和 Codex,我做了个 Agent 资产库:能预览、能投递、能追更新
ai编程·claude·mcp
huaweichenai4 小时前
php搭建mcp服务
开发语言·php·ai编程
ZzT4 小时前
嫌推理慢就把 effort 调高?effort 管的是想多少,不是想多快
openai·ai编程·claude
孔令飞4 小时前
OneX Harness AI Coding 工具介绍
云原生·go·ai编程
9i编程5 小时前
6. 带 AI 从零搭低代码平台·踩坑实录:实测与大回归,实操又跑偏逐条清零
人工智能·openai·ai编程