104 Codex Hooks 自动化
利用 Codex 的 Hook 事件系统,在开发流程的关键节点自动触发脚本,打造"无人值守"的开发自动化。
学习目标
完成本课程后,你将能够:
一、Hook 事件模型
Codex Hooks 允许你在 Codex 执行操作的前后**自动触发**自定义脚本。这类似于 Git Hooks,但覆盖的范围更广。
Hook 事件类型
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
| `PreToolUse` | 某个工具被执行**之前** | 阻止危险操作、二次确认 |
| `PostToolUse` | 某个工具执行**完成之后** | 自动格式化、自动 lint、记录日志 |
| `Notification` | 特定状态变化时 | 桌面通知、Slack 推送 |
| `UserPromptSubmit` | 用户提交 Prompt 后 | 自动补充上下文 |
| `Stop` | Codex 完成任务后 | 清理操作、总结汇报 |
事件生命周期
User Prompt → UserPromptSubmit Hook
↓
Codex 规划操作 → PreToolUse Hook(每次工具调用前)
↓
工具执行
↓
PostToolUse Hook(每次工具调用后)
↓
...重复(多步骤任务)
↓
任务完成 → Stop Hook二、配置 PreToolUse / PostToolUse
在 codex.yaml 中配置
# codex.yaml — Hooks 配置部分
hooks:
# —— 工具使用前 ——
PreToolUse:
- matcher: "Write|Edit" # 匹配写文件操作
command: "node .codex/hooks/check-before-write.js"
description: "写文件前检查是否在正确分支"
- matcher: "Bash" # 匹配终端命令
command: "node .codex/hooks/check-dangerous-cmd.js"
description: "阻止 rm -rf、force push 等危险命令"
# —— 工具使用后 ——
PostToolUse:
- matcher: "Write|Edit" # 文件修改后
command: "npx prettier --write ${CODEX_MODIFIED_FILES}"
description: "自动格式化修改的文件"
- matcher: "Write|Edit"
command: "npx eslint --fix ${CODEX_MODIFIED_FILES}"
description: "自动修复 ESLint 问题"
# —— 任务完成 ——
Stop:
- command: "node .codex/hooks/generate-summary.js"
description: "生成任务完成摘要"
# —— 通知 ——
Notification:
- matcher: "Stop"
command: "osascript -e 'display notification \"Codex 任务完成\"'"
description: "macOS 桌面通知"Codex 环境变量
Hook 脚本中可以使用的环境变量:
| 变量 | 说明 |
|---|---|
| `CODEX_MODIFIED_FILES` | 当前操作修改的文件列表 |
| `CODEX_PROJECT_ROOT` | 项目根目录路径 |
| `CODEX_TOOL_NAME` | 触发的工具名称 |
| `CODEX_SESSION_ID` | 当前会话 ID |
| `CODEX_MODEL` | 当前使用的模型 |
三、实战:Git Pre-commit 自动检查
Hook 脚本 1:检查是否在 feature 分支
// .codex/hooks/check-branch.js
const { execSync } = require('child_process');
try {
const branch = execSync('git branch --show-current', { encoding: 'utf-8' }).trim();
// 禁止在 main 或 master 分支直接修改文件
if (branch === 'main' || branch === 'master') {
console.error('❌ [Codex Hook] 禁止在 main/master 分支直接修改文件!');
console.error(' 请先切换到 feature 分支。');
process.exit(1); // 非 0 退出码会阻止 Codex 继续操作
}
console.log(`✅ [Codex Hook] 当前分支: ${branch},检查通过`);
} catch (err) {
console.error('❌ [Codex Hook] Git 分支检查失败:', err.message);
process.exit(1);
}Hook 脚本 2:危险命令拦截
// .codex/hooks/check-dangerous-cmd.js
const dangerousPatterns = [
{ pattern: /rm\s+-rf\s+\//, message: '禁止 rm -rf / 操作' },
{ pattern: /git\s+push\s+--force/, message: '禁止 git push --force' },
{ pattern: /git\s+reset\s+--hard/, message: '禁止 git reset --hard' },
{ pattern: />\s*\/dev\/sda/, message: '禁止写入磁盘设备' },
{ pattern: /DROP\s+(TABLE|DATABASE)/i, message: '禁止 DROP TABLE/DATABASE' },
];
const command = process.argv[2] || '';
for (const { pattern, message } of dangerousPatterns) {
if (pattern.test(command)) {
console.error(`🚨 [Codex Hook] ${message}`);
console.error(` 命令: ${command}`);
process.exit(1);
}
}
console.log('✅ [Codex Hook] 安全检查通过');在 codex.yaml 中启用
hooks:
PreToolUse:
- matcher: "Write|Edit"
command: "node .codex/hooks/check-branch.js"
- matcher: "Bash"
command: "node .codex/hooks/check-dangerous-cmd.js"
PostToolUse:
- matcher: "Write|Edit"
command: "npx prettier --write ${CODEX_MODIFIED_FILES}"四、自定义 Hook 脚本
自动生成变更日志
// .codex/hooks/generate-summary.js
const { execSync } = require('child_process');
const fs = require('fs');
// 获取 Codex 修改的文件
const modifiedFiles = (process.env.CODEX_MODIFIED_FILES || '').split(',').filter(Boolean);
const summary = {
timestamp: new Date().toISOString(),
model: process.env.CODEX_MODEL || 'unknown',
session_id: process.env.CODEX_SESSION_ID || 'unknown',
files_changed: modifiedFiles,
git_diff: ''
};
try {
summary.git_diff = execSync('git diff --stat', { encoding: 'utf-8' }).trim();
} catch (e) {
summary.git_diff = '(无法获取 git diff)';
}
// 追加到变更日志
const logEntry = `
## ${summary.timestamp}
- 模型: ${summary.model}
- 修改文件: ${summary.files_changed.join(', ')}
- 变更统计: ${summary.git_diff}
`;
fs.appendFileSync('.codex/change-log.md', logEntry, 'utf-8');
console.log('📝 [Codex Hook] 变更日志已更新');自动类型检查
# 在 PostToolUse 中配置
PostToolUse:
- matcher: "Write|Edit"
command: |
if echo "${CODEX_MODIFIED_FILES}" | grep -q "\.tsx\?$\|\.ts$"; then
npx tsc --noEmit --pretty
echo "✅ [Codex Hook] TypeScript 类型检查通过"
fi五、Hook 最佳实践
实践指南
| 原则 | 说明 |
|---|---|
| **快速失败** | Hook 脚本应该在 3 秒内完成,不做耗时操作 |
| **明确报错** | 阻止操作时给出清晰的错误信息和修正指引 |
| **幂等性** | Hook 重复执行不应产生副作用 |
| **可跳过** | 提供环境变量开关 `CODEX_SKIP_HOOKS=1` |
| **日志记录** | 将 Hook 执行结果写入 `.codex/hooks.log` |
| **版本控制** | `.codex/hooks/` 目录纳入 Git,团队成员共享 |
跳过 Hook
# 临时跳过所有 Hook
CODEX_SKIP_HOOKS=1 codex
# 在 Hook 脚本中检查
if (process.env.CODEX_SKIP_HOOKS === '1') {
console.log('⏭️ [Codex Hook] Hooks 已跳过');
process.exit(0);
}Hook 调试
# 开启调试模式
hooks:
debug: true # 打印每个 Hook 的执行详情六、本课总结
**核心要点:**
- Codex Hooks 在工具执行前后自动触发,覆盖 PreToolUse、PostToolUse、Stop 等事件
- PreToolUse 用于安全检查(阻止危险命令、分支保护)
- PostToolUse 用于自动格式化(prettier、eslint、类型检查)
- Hook 脚本是普通 shell/node 脚本,通过退出码控制行为(0=通过,非0=阻止)
- 将 `.codex/hooks/` 纳入 Git,团队成员共享相同的自动化规则
- 用 `CODEX_SKIP_HOOKS=1` 在必要时跳过所有钩子
**下一步:** 下一课将学习 MCP(Model Context Protocol)工具扩展,让 Codex 连接外部 API、数据库和自定义工具。