module2
25 分钟

Codex Hooks 自动化

学习进度

当前模块0/6 课
总进度0/36 课
学习时长0 分钟

学习目标

  • 理解 Codex Hook 的事件模型和生命周期
  • 配置 PreToolUse 和 PostToolUse 钩子
  • 创建 Git pre-commit 自动检查钩子
  • 编写自定义 Hook 脚本并调试

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、数据库和自定义工具。

上一课
下一课