Codex Hooks 钩子系统实战指南:用生命周期回调精准控制 AI 编程助手的行为

引言

在日常使用 Codex 的过程中,你是否遇到过这些场景:希望 Codex 每次完成响应后播放提示音,避免反复检查窗口;想在 AI 执行高风险命令前自动进行安全审计;需要在每次会话启动时自动加载项目上下文。这些需求都可以通过 Codex 的 Hooks 钩子系统 来实现。

Hooks 是 Codex 从 v0.114 版本开始引入的实验性功能,它允许开发者在 Codex 会话的关键生命周期节点上挂载自定义脚本,实现对 AI 行为的精细化和自动化控制。本文将深入介绍 Codex Hooks 的配置方式、支持的事件类型以及实际应用场景。

Hooks 是什么

Hooks(钩子)是一种事件驱动的回调机制。你可以在 Codex 的生命周期节点(如会话启动、命令执行前后、会话结束等)注册自定义命令或脚本,Codex 会在对应的时机自动执行它们。根据钩子的返回结果,你可以选择放行、阻止或修改 Codex 的后续行为。

Hooks 的设计灵感来源于 Git Hooks 和 Claude Code 的 Hooks 系统,它为 Codex 提供了一种轻量级但极为灵活的扩展方式——无需开发插件,仅通过配置文件和 Shell 脚本即可实现丰富的自动化能力。

启用 Hooks 功能

由于 Hooks 目前处于实验阶段,需要通过特性开关来启用:

codex -c features.codex_hooks=true

你也可以在 config.toml 中持久化这个设置:

[features]
codex_hooks = true

Hooks 的配置方式

Codex 支持两种 Hooks 配置方式:独立的 hooks.json 文件和嵌入在 config.toml 中的配置。

方式一:hooks.json(推荐)

.codex 目录下创建 hooks.json 文件:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo 'session started' >> /tmp/codex.log",
            "statusMessage": "Initializing session...",
            "timeout": 10
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo 'turn ended' >> /tmp/codex.log",
            "statusMessage": "Wrapping up turn...",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

方式二:config.toml 内嵌

[hooks]
SessionStart = [
  { type = "command", command = "bash ~/.codex/on-start.sh", timeout = 30 }
]

Stop = [
  { type = "command", command = "bash ~/.codex/on-stop.sh", timeout = 10 }
]

支持的生命周期事件

Codex Hooks 围绕以下生命周期事件展开,你可以根据需求选择合适的触发时机:

1. SessionStart —— 会话启动

在每次 Codex 会话开始时触发。这是初始化环境的理想时机,比如加载项目特定配置、设置环境变量或预热缓存。

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash .codex/hooks/on-session-start.sh",
            "statusMessage": "Setting up project environment...",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

对应的 on-session-start.sh 脚本示例:

#!/bin/bash
# 加载项目环境变量
export $(cat .env | xargs)
# 预热类型检查
npx tsc --noEmit &
# 记录会话日志
echo "[$(date)] Session started" >> .codex/session.log

2. Stop —— 会话/轮次结束

当 Codex 完成一轮对话或工具调用后触发。这是通知用户 AI 已完成工作的最佳时机。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "powershell -NoProfile -Command \"(New-Object System.Media.SoundPlayer 'C:\\Windows\\Media\\Windows Exclamation.wav').PlaySync()\"",
            "statusMessage": "Task completed",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

在 macOS 上可以播放系统提示音:

{
  "type": "command",
  "command": "afplay /System/Library/Sounds/Glass.aiff",
  "timeout": 5
}

在 Linux 上可以使用 notify-send 发送桌面通知:

{
  "type": "command",
  "command": "notify-send 'Codex' 'Task completed'",
  "timeout": 5
}

3. PreToolUse —— 工具调用前

在 Codex 准备执行某个工具(如 Shell 命令、文件编辑)之前触发。这是实现安全审计和执行策略的核心钩子——你可以在此检查、拦截甚至修改即将执行的操作。

PreToolUse 支持 matcher 字段来过滤特定的工具类型:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash .codex/hooks/protect-files.sh",
            "statusMessage": "Checking file write permissions..."
          }
        ]
      },
      {
        "matcher": "Shell",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .codex/hooks/audit-command.py",
            "statusMessage": "Auditing shell command..."
          }
        ]
      }
    ]
  }
}

钩子脚本可以通过 stdin 接收上下文信息(JSON 格式),通过 stdout 返回决策:

stdin 输入示例:

{
  "tool_name": "Shell",
  "tool_input": { "command": "rm -rf /tmp/build/*" },
  "session_id": "sess-abc123",
  "cwd": "/home/user/project"
}

stdout 输出格式:

{
  "decision": "deny",
  "reason": "Deletion of build artifacts must be confirmed manually"
}

退出码约定:

  • 退出码 0:允许操作继续
  • 退出码 2:拒绝操作执行

以下是一个实用的文件保护脚本 protect-files.sh

#!/bin/bash
INPUT=$(cat)
TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input')
FILE_PATH=$(echo "$TOOL_INPUT" | jq -r '.file_path // empty')

# 定义受保护的文件列表
PROTECTED=(
  "package.json"
  "tsconfig.json"
  ".github/workflows/"
  "src/core/"
)

for pattern in "${PROTECTED[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo '{"decision": "deny", "reason": "Protected file: '$FILE_PATH'"}'
    exit 2
  fi
done

echo '{"decision": "allow"}'
exit 0

4. PostToolUse —— 工具调用后

在工具执行完成后触发,用于记录日志、触发后续任务或做执行结果校验。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Shell",
        "hooks": [
          {
            "type": "command",
            "command": "bash .codex/hooks/log-command.sh",
            "statusMessage": "Logging command execution...",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

log-command.sh 脚本示例:

#!/bin/bash
INPUT=$(cat)
echo "[$(date)] Tool: $(echo $INPUT | jq -r '.tool_name')" >> .codex/command-history.log
echo "Exit code: $(echo $INPUT | jq -r '.exit_code')" >> .codex/command-history.log
exit 0

Notification 通知钩子

除了生命周期事件钩子,Codex 还提供了一个简化的通知机制:notify 配置项。它在 Codex 完成 agent-turn 时触发,适合播放提示音或发送桌面通知。

config.toml 中配置:

notify = ["bash", "-lc", "notify-send 'Codex' 'Task completed'"]

或者在 Windows 上:

notify = [
  "powershell",
  "-NoProfile",
  "-ExecutionPolicy", "Bypass",
  "-Command",
  "(New-Object System.Media.SoundPlayer 'C:\\Windows\\Media\\Windows Exclamation.wav').PlaySync()"
]

通知钩子会收到一个 JSON 对象作为参数,包含事件类型信息:

{
  "type": "agent-turn-complete"
}

你可以根据事件类型做不同的处理:

#!/bin/bash
NOTIFICATION_JSON="$1"
EVENT_TYPE=$(echo "$NOTIFICATION_JSON" | jq -r '.type')

if [ "$EVENT_TYPE" = "agent-turn-complete" ]; then
  notify-send "Codex" "Response ready"
fi

企业级管理:Managed Hooks

对于团队或企业环境,管理员可能希望控制哪些 Hooks 可以在 Codex 中运行。Codex 提供了 allow_managed_hooks_only 配置项,当设置为 true 时,只有通过 requirements.toml 和 managed config 层定义的 Hooks 才会生效。

requirements.toml 中配置:

allow_managed_hooks_only = true

这样可以确保:

  • 安全性:用户自定义的 Hooks 不会被执行,防止恶意脚本注入
  • 合规性:团队可以强制执行统一的审计日志策略
  • 可控性:管理员集中管理所有可执行的 Hooks

实战场景

场景一:代码质量门禁

在每次 PreToolUse 时自动运行 lint 检查,如果代码格式不达标则阻止继续操作:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash .codex/hooks/lint-gate.sh",
            "statusMessage": "Running lint check...",
            "timeout": 30
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# lint-gate.sh
FILES_CHANGED=$(git diff --name-only --diff-filter=M)
if [ -z "$FILES_CHANGED" ]; then
  exit 0
fi

# 运行 ESLint
npx eslint $FILES_CHANGED --max-warnings 0 2>/dev/null
if [ $? -ne 0 ]; then
  echo '{"decision": "deny", "reason": "ESLint found errors. Fix them first."}'
  exit 2
fi
echo '{"decision": "allow"}'
exit 0

场景二:敏感操作二次确认

拦截所有 rmdropdelete 等危险命令,要求用户额外确认:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Shell",
        "hooks": [
          {
            "type": "command",
            "command": "bash .codex/hooks/dangerous-ops.sh",
            "statusMessage": "Checking command safety..."
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# dangerous-ops.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')

DANGEROUS_PATTERNS=("rm -rf /" "DROP DATABASE" "DROP TABLE" "git push --force" "shutdown")

for pattern in "${DANGEROUS_PATTERNS[@]}"; do
  if [[ "$COMMAND" == *"$pattern"* ]]; then
    echo "{\"decision\": \"deny\", \"reason\": \"Dangerous command detected: $COMMAND. Manual confirmation required.\"}"
    exit 2
  fi
done

echo '{"decision": "allow"}'
exit 0

场景三:跨平台通知系统

编写一个通用的通知脚本 notify.sh,自动检测操作系统并播放对应的提示音:

#!/bin/bash
EVENT_TYPE=$(echo "$1" | jq -r '.type // "unknown"')

notify() {
  case "$(uname -s)" in
    Darwin)
      afplay /System/Library/Sounds/Glass.aiff
      osascript -e "display notification \"Codex: $EVENT_TYPE\" with title \"Codex\""
      ;;
    Linux)
      notify-send "Codex" "Event: $EVENT_TYPE"
      paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null
      ;;
    CYGWIN*|MINGW*|MSYS*)
      powershell -c "(New-Object System.Media.SoundPlayer 'C:\\Windows\\Media\\Windows Exclamation.wav').PlaySync()"
      ;;
  esac
}

notify

场景四:Git 提交前检查

PreToolUse 时检查 Codex 是否即将执行 git commit,如果是则自动运行测试套件:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Shell",
        "hooks": [
          {
            "type": "command",
            "command": "bash .codex/hooks/pre-commit-check.sh",
            "statusMessage": "Running pre-commit checks...",
            "timeout": 120
          }
        ]
      }
    ]
  }
}
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')

if [[ "$COMMAND" == *"git commit"* ]]; then
  echo "Running pre-commit validation..."
  npm test 2>&1
  if [ $? -ne 0 ]; then
    echo '{"decision": "deny", "reason": "Tests failed. Commit aborted."}'
    exit 2
  fi
  npm run lint 2>&1
  if [ $? -ne 0 ]; then
    echo '{"decision": "deny", "reason": "Lint failed. Commit aborted."}'
    exit 2
  fi
fi

echo '{"decision": "allow"}'
exit 0

注意事项与最佳实践

超时设置:每个 Hook 都应设置合理的 timeout,避免因脚本卡死导致 Codex 会话无法继续。

幂等性:Hook 脚本应尽量设计为幂等的,同一事件多次触发不会产生副作用。

日志记录:在 Hook 脚本中添加日志记录,方便问题排查。

错误处理:确保 Hook 脚本在任何情况下都能正常退出(即使失败也应返回明确的退出码),避免阻塞 Codex 主流程。

性能考量PreToolUse 钩子会直接影响 Codex 的响应速度,避免在其中执行耗时操作。长时间运行的任务应考虑异步触发。

跨平台兼容:如果团队成员使用不同操作系统,应提供对应的脚本适配方案。

总结

Codex Hooks 钩子系统虽然目前仍处于实验阶段,但它为 AI 编程助手提供了强大的可编程扩展能力。通过合理配置 SessionStartStopPreToolUsePostToolUse 等事件钩子,你可以:

  • 建立代码质量门禁,在 AI 修改代码前自动运行 lint 和测试
  • 实现安全审计机制,拦截高风险操作
  • 添加通知系统,在任务完成时获得及时提醒
  • 构建团队级别的合规策略,统一管理 Hooks 执行权限

随着 Hooks 系统的持续完善,未来可能会支持更多的生命周期事件(如 PreModelCallPostModelCall)和异步执行模式。现在就开始尝试为你的项目配置 Hooks,让 Codex 更好地融入你的开发工作流吧。