Skip to content
围炉聊科技微信公众号二维码关注公众号,获取新文章推送
添加我为微信朋友扫描二维码,添加我为朋友

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在对话框下方展示的钩子图标上标注了一个小黄点提示我需要审核。

1.00

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

1.00

对于 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")

1.00

以下是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拦截了

image.png
5. 运行 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工具执行前风险拦截、参数检查
PermissionRequestCodex 请求权限时审批策略
PostToolUse工具执行后检查结果、代码规范
PreCompact上下文压缩前保存关键任务状态
PostCompact上下文压缩后恢复任务上下文
SubagentStart子 Agent 启动注入角色规范
SubagentStop子 Agent 完成收集结果、质量反馈
StopCodex 本轮响应即将结束检查任务完成情况
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、数据库、浏览器