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、数据库、浏览器
相关推荐
小虎AI生活9 分钟前
Anatomy 接入 WorkBuddy 实操:一句话生成 12 零件可交互科普模型(附2组提示词)
aigc·ai编程
OpsEye26 分钟前
别再挨个充会员了:多模型编码,最麻烦的从来不是选模型
javascript·ai编程
程序员老刘39 分钟前
GUI的时代过去了
ai编程
用户3721574261351 小时前
C# 实现 Excel 单元格保护:锁定公式并允许编辑指定区域
c#·ai编程·编程语言
浪遏3 小时前
06|AI 失败不扣次,这句话要靠什么兑现
ai编程
浪遏3 小时前
01|把简历操作交给 AI:流萤简历的 CLI 自动化实践
ai编程
ZzT4 小时前
AI 是放大器:基础越扎实,放大效果越明显
人工智能·ai编程
疯华整猫5 小时前
把二十七个 init 从首帧前面搬走:用 Trae 把启动优化钉进 CI
android·性能优化·ai编程
灵感__idea5 小时前
“小猫补光灯”:App Store 付费榜第一的产品故事
ai编程·claude·cursor
threerocks5 小时前
一个人指挥一支 AI 剧组:Gemini 4 + Omni 1.2 剧情短片制作保姆级教程
aigc·openai·ai编程