故障处理

Codex hooks 不触发?事件匹配、加载信任与 exec/CI 验证

配置了 Codex hooks 却没反应?按事件与 matcher、配置来源、项目和定义信任、脚本执行环境逐层排查,提供最小日志探针与 exec/CI 对照方法,区分实际派发、策略拦截和旧版本个案。

把 hooks.json 写好后,Codex 仍没有运行检查脚本;或者交互式 CLI 有日志,codex exec、CI 中却什么也没有。先分清三件事:动作是否产生匹配的事件、当前运行是否加载并允许这条 hook、脚本是否真正启动并按约定返回。 文件存在、列表可见、模型说“已检查”,都不能单独证明钩子执行成功。

本文处理“Codex hooks 不触发”“Codex 钩子没执行”、codex hooks not firing 和 codex exec hooks not firing。下面以本地 Codex CLI 的生命周期 hooks 为主;保留现有配置,先检查,再在获准的独立测试项目中做最小验证。

先按现象找下一步

你看到的现象 从哪里查
/hooks 没有预期来源或处理器 第 1、3 步查执行端、文件结构、功能开关和有效配置层
列表里有,但标记待审、禁用或定义已变化 第 3 步分别核对项目层信任和当前定义信任
只有某种工具、启动方式或事件不触发 第 2 步核对事件、工具名与 matcher
日志没有新增,手工运行脚本也报错 第 4 步先查解释器、路径、stdin 和输出目录权限
交互式成功,exec/CI 没有记录 第 5 步对照主机、用户、配置目录、工作目录与版本
有日志,但该拦截的动作仍执行了 第 6 步验证同步处理、返回协议和实际副作用

hooks 跟随会话或工具事件运行;“每天几点启动一个任务”属于定时任务触发排查。Skills 的发现与选用也有独立专题,不要把三者当作同一种触发机制。

1. 确认你检查的是实际执行端

适用条件: 本机终端能运行,Codex 任务却没反应,或者还不知道是谁执行了 hook。

先记录客户端、版本、交互式还是 exec,以及本地、WSL、SSH、容器或 CI runner。CLI 在对应环境运行:

codex --version
codex exec --help

在 macOS/Linux 的 Bash 或 zsh 中,再查当前入口与目录:

command -v codex
pwd
printf 'CODEX_HOME=%s\n' "${CODEX_HOME:-<未设置>}"

预期结果: 能确认实际二进制、工作目录,以及是否改用了另一份 Codex 配置目录。Windows PowerShell 可用 Get-Command codex、Get-Location 和 $env:CODEX_HOME 查看对应信息。WSL 内的 Linux 配置与 Windows 宿主配置分别核对;CI 中在实际执行步骤里检查,不用自己电脑的结果替代。

/hooks 是官方 CLI hook 浏览器的入口。其他客户端没有这个菜单,不等于所有 hooks 都坏了。尤其 Work Cloud 编排的管理员远程 MCP hooks 与本地 command hooks 有不同支持范围,不能把本地 hooks.json 搬过去就认为会执行;按官方 Hooks 的 managed hooks 说明核对具体工作流。

若两端用的版本或入口不同,先按版本更新后未变化定位,不直接重装或清空配置。

2. 核对事件和 matcher:你等的动作会产生这个事件吗?

适用条件: hook 已能看到,但只有部分动作没反应。

按官方 matcher 与工具覆盖范围逐项对照。matcher 是正则,部分事件匹配的是工具名,部分匹配的是启动来源,不能全部照搬 Bash:

目标动作 匹配与验证方法
shell/exec_command 调用前后 用 PreToolUse/PostToolUse,工具匹配名为 Bash,例如 ^Bash$
apply_patch 编辑文件 可匹配 apply_patch、Edit 或 Write;事件输入仍报告 apply_patch
某个 MCP 工具 匹配实际 MCP 工具名,不把服务器显示名当成工具名;连接问题见 MCP 专题
新建、恢复、压缩后启动 SessionStart 的来源包括 startup、resume、clear、compact、fork;只匹配 startup 就不包含恢复
提交用户提示词、轮次结束或中断 UserPromptSubmit、Stop、Interrupt 当前不使用 matcher;配置的过滤条件会被忽略
产生审批请求 PermissionRequest 只在确实需要审批时运行,不是每次命令都会触发

再排除两个容易误判的场景:

  • WebSearch 等 hosted 工具不经过本地 PreToolUse/PostToolUse 路径,不能用 shell hook 验收它们。
  • write_stdin 是已启动命令的输入或轮询通道,不会重新触发 PreToolUse;原命令稍后结束时,某次轮询可能送达它的 PostToolUse。不要把轮询次数当 hook 次数。

预期结果: 本次动作对应受支持事件,matcher 对准实际名称或来源。若没有命中,先修正本条定义并重新审查,再重复相同动作;若本来不支持,不以反复重启或扩大权限解决。官方还说明部分专用工具路径可不走默认 hooks,因此不能把它当作覆盖所有执行通道的完整防护边界。

3. 分开检查“加载了”和“允许运行”

来源与文件结构

适用条件: /hooks 看不到这条处理器,或不确定它来自哪份配置。

官方当前支持在有效配置层旁发现 hooks.json,也支持 config.toml 内联 [hooks]。常见位置为用户目录下的 ~/.codex/hooks.json、~/.codex/config.toml,以及项目的 .codex/hooks.json、.codex/config.toml;自定义 CODEX_HOME 时先定位实际目录。插件也可带入 hooks。

JSON 必须有顶层 hooks 对象,内部再按事件、matcher 组和处理器组织。同层 JSON 与内联 TOML 会合并并产生启动提醒,多层匹配 hooks 也会一起运行;不能套用“高层整组覆盖低层”的想象。 重复执行时先核对来源,不靠再复制一份定义修复。

已安装 Python 3 时,可验证指定 JSON 的语法。替换为真实文件路径;这条命令只解析文件:

python3 -m json.tool "/absolute/path/to/hooks.json" > /dev/null

预期结果: 无解析错误;但这只证明 JSON 语法,不证明有正确的 hooks 层级、有效事件、支持的处理器或已被当前 Codex 加载。当前文档支持 command 与 mcp_tool;prompt、agent 类型即使能解析,也会跳过。Windows 使用实际已安装的解释器,PowerShell 将输出重定向到 $null,不要照抄 /dev/null。

在同一 CLI 中打开 /hooks,按事件检查处理器及来源;有配置层疑问时用 /debug-config 查看有效层,再参照 config.toml 加载排查。检查有效配置中是否显式关闭 [features].hooks,以及管理员是否要求只允许 managed hooks。当前 hooks 默认启用;管理员策略限制时交给维护者处理,不通过用户开关覆盖它。

项目信任与定义信任

适用条件: 文件结构正确,但仍看不到项目来源,或处理器列出后没有运行。

官方信任规则区分两层:

  1. 项目 .codex/ 层需受信任才加载;用户与系统层按各自有效配置加载。
  2. 非 managed hook 的当前定义还需经过审查与信任。定义改变后哈希变化,会再次待审并被跳过;插件启用也不自动完成这一步。

在 /hooks 查看是否禁用、待审或已变化。先审查真实命令、脚本、参数及来源,再按本项目获准的方式信任确切定义;managed hook 由策略管理,不能从用户浏览器禁用。不要自动信任未知脚本,也不要把绕过信任的参数作为默认修复。

预期结果: 目标来源已加载,处理器启用,当前定义的审查状态符合要求。修改配置后退出并重新启动目标 CLI,再用 /hooks 核对本次新运行;只刷新另一个编辑器窗口,不能证明 CI 或后台进程加载了新配置。

4. 用最小探针验证脚本与实际派发

适用条件: 事件、来源和信任已确认,仍缺少 hook 真正启动的证据。

先在获准的独立测试项目中保存下面的 hooks_probe.py。它只读取 stdin JSON,并在脚本旁追加一个小型 .jsonl 文件,不记录提示词、工具参数、凭据或完整对话。确认该目录允许写入;不要把生产策略脚本改成这个探针。

import json
import sys
from datetime import datetime, timezone
from pathlib import Path

event = json.load(sys.stdin)
record = {
    "at": datetime.now(timezone.utc).isoformat(),
    "event": event.get("hook_event_name"),
    "tool": event.get("tool_name"),
    "source": event.get("source"),
    "cwd": event.get("cwd"),
    "has_session": bool(event.get("session_id")),
}
log_path = Path(__file__).resolve().with_suffix(".jsonl")
with log_path.open("a", encoding="utf-8") as stream:
    stream.write(json.dumps(record, ensure_ascii=False) + "\n")

先手工检查脚本,不把手工记录当成 Codex 派发

macOS/Linux 下,把下面两处绝对路径换成已确认的 Python 3 和探针路径。路径有空格时保留引号:

printf '%s\n' '{"hook_event_name":"PreToolUse","tool_name":"Bash","source":"manual-test","cwd":"/your/test-project"}' | \
  "/absolute/path/to/python3" "/absolute/path/to/hooks_probe.py"

预期结果: 脚本退出 0、stdout 为空,旁边日志新增一行,source 为 manual-test、has_session 为 false。解释器不存在、JSON 读取失败或目录不可写时,先按实际 stderr 修复脚本运行环境;没有日志也可能是写日志失败,不能直接判定 Codex 没派发。

再接入测试 hook,观察新记录

下面是供合并审查的 JSON 结构示例。三个 command 都要换成真实路径;它们是 hook 执行的 shell 命令字符串,JSON 中的 \" 用于保留命令内路径引号。不要覆盖已有 hooks.json,也不要重复添加同一条生产策略。先备份配置,只把必要测试处理器合并进获准测试项目的对应事件数组:

{
  "hooks": {
    "SessionStart": [{
      "matcher": "^startup$",
      "hooks": [{
        "type": "command",
        "command": "\"/absolute/path/to/python3\" \"/absolute/path/to/hooks_probe.py\""
      }]
    }],
    "PreToolUse": [{
      "matcher": "^Bash$",
      "hooks": [{
        "type": "command",
        "command": "\"/absolute/path/to/python3\" \"/absolute/path/to/hooks_probe.py\""
      }]
    }],
    "PostToolUse": [{
      "matcher": "^Bash$",
      "hooks": [{
        "type": "command",
        "command": "\"/absolute/path/to/python3\" \"/absolute/path/to/hooks_probe.py\""
      }]
    }]
  }
}

按第 3 步审查新定义后,重新启动目标 CLI。记录测试前日志行数与时间,在新会话明确要求“仅调用 shell 运行 pwd 并报告结果,不修改文件”。这是模型任务,可能使用账户额度;若不准备发起任务,可停在手工脚本验证阶段,如实标记尚未验证派发。

预期结果: 本次新会话出现 SessionStart,实际 shell 调用产生 PreToolUse,命令结束后出现 PostToolUse。对照新增记录的时间、事件、Bash、cwd 和会话字段;不把之前的 manual-test 或上次运行日志算进来,也不要求并发日志按固定顺序排列。

只看到 SessionStart 时,先核对模型是否真的调用 shell;只回复一段 pwd 示例不会产生工具事件。完全没新增时,回查当前定义信任、实际配置来源与日志写入错误;不要让模型一句“hook 已运行”代替记录。验证后仅移除本次新增的探针处理器,保留其他 hooks 与已获准策略,再启动一次确认配置恢复。

5. 交互式成功、exec/CI 失败:一次只比较一个差异

适用条件: 同一只读任务在交互式有证据,自动运行却没有。

先对齐第 1 步的版本和入口,再逐项核对:

  • 主机、用户与配置目录: runner 是否有同一份获准配置?换了 CODEX_HOME、临时容器或用户后,不假设旧的定义信任记录随之存在;不要为了验证复制整份私人认证目录。
  • 实际项目与 cwd: hook 命令以会话 cwd 执行。相对脚本路径从子目录启动时可能失效;优先使用已存在、正确引用的绝对路径,项目脚本也可按官方建议从 Git 根定位。worktree 不同先看目录定位。
  • 解释器与环境: 终端 alias、虚拟环境和交互 shell 的 PATH 不一定进入 hook。比较实际解释器路径、必要变量是否存在、文件是否挂载,不打印整个环境或 secret;参照任务找不到 npm、node、python。Windows 用实际 Python 路径,并核对 commandWindows/command_windows,不复制 Unix shell 语法。
  • 脚本与生命周期: 脚本是否从 stdin 读取一个 JSON?是否因外部服务、读取输入或错误路径而卡住?按实际退出状态和 hook 错误查超时;timeout 单位为秒。若设置了 async,输出可能延后到安全时点,会话结束还可能取消未完成后台 hook。

对齐后,在该 runner 的获准测试项目中,用原有审批与安全策略运行同一只读提示词。若已有基于 CLI 的任务采集方式,可保留 stdout、stderr 与退出状态;本机帮助支持 --json 时,可用 JSONL 留下工具调用相关证据。下面是 Bash/zsh 的一次性采集示例;先完成正常认证与 hook 审查,把 --cd 换成获准的测试仓库绝对路径。它会发起模型任务,在新临时目录保存输出,不修改原有安全策略:

probe_run_dir="$(mktemp -d)"
if codex exec --json --cd "/absolute/path/to/test-project" \
  "仅调用 shell 运行 pwd 并报告结果,不修改文件" \
  >"$probe_run_dir/exec.jsonl" 2>"$probe_run_dir/exec.stderr"; then
  probe_exit=0
else
  probe_exit=$?
fi
printf 'exit=%s evidence=%s\n' "$probe_exit" "$probe_run_dir"

查看该目录的事件和 stderr,并与探针旁本次新增日志对应。退出 0 不证明 hook 派发;认证失败或任务在工具调用前结束时,也不能据此判断 PreToolUse 有问题。不要只检查最后一条自然语言回复。输出为空或提前退出见 exec 输出与退出专题。

成功标准: 能把本次 runner 的真实工具调用,与同次新增的 hook 记录对应起来。若 TUI 成功而 exec 失败,只说明还存在运行条件或版本差异,不能直接认定某一个历史信任传递 bug。失败时保存最小配置、脚本自身结果及脱敏错误,继续第 7 步核版本;不改成全权限模式来制造“通过”。

6. 有 marker 不代表策略已拦住:另验返回协议与副作用

适用条件: 你的目标是禁止某类命令,而不只是确认 hook 被调用。

探针追加日志只证明脚本走到写入处。还要核对当前事件输出协议:例如同步 PreToolUse 的拒绝可使用下面结构,示例仅说明返回格式,不是直接替换你现有策略的脚本:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "测试规则拒绝这条指定动作"
  }
}

按当前官方规则检查这些边界:

  • PreToolUse 的普通 stdout 文本不会变成拒绝决定;返回错误字段或格式不能等同于成功拦截。
  • PostToolUse 发生在执行之后,不能撤销已经产生的副作用;后台 async hook 不能阻止、审批或改写触发它的动作。
  • MCP hook 使用已有连接;缺少服务器、工具不可用或调用错误不等于明确拒绝,不能假设故障一定阻断后续工具。

具体验收: 在获准的临时项目中,选一条只会创建临时 marker 的无害动作作为拒绝目标,同时选一条允许的只读动作作对照。检查调用前的策略返回、目标 marker 是否真的没有创建、允许动作是否按预期完成;不要拿生产删除、提交或发布当测试。只看到 hook 日志,但被拒动作仍执行,策略验收就是失败,应停止依赖该保护并交维护者排查,不继续危险任务。

7. 旧版本 exec 个案怎么用,哪些结论不能外推

官方仓库 issue #26452 于 2026-06-04 报告:Linux、隔离配置下,CLI 0.137.0 与 0.138.0-alpha.2 即使 JSON 层级正确仍未派发 hooks;后来评论另提到 0.139.0。这些是报告者的测试范围,不是当前全部平台的结论。

PR #26434 于 2026-06-16 合入 main,处理特定路径:显式传入的 hook 信任绕过标志在 exec 创建或恢复 thread、重新加载配置时丢失。PR 描述包含 start/resume 配置传递测试及 SessionStart marker 回归测试;issue 后续有维护者修复说明,并以重复问题关闭。

这不表示所有 hooks 无反应都由该 bug 引起,也不表示应该加绕过参数。 PR 合并不能单独确定你正在用的发行版本已含修复;本文不据此指定“升级到某版本必好”。若前面分流都通过但仍出现交互/exec 差异,带实际版本、同条件最小复现与运行证据核对对应发布记录,再按原安装渠道更新和复验,保留回退方案,不默认降级、删状态或增加重试。

当前字段和行为以官方 Hooks 页面为准;该页提醒 main 分支 schema 可能领先已发布版本。遇到本机帮助不支持、实际行为与文档不一致时,明确记录差异,不把实验版、旧 issue 或 main 源码当作所有客户端共同契约。

最终验收与求助信息

逐层确认:事件适用且 matcher 正确;目标来源已加载、启用并完成必要审查;脚本在实际环境能读取 stdin 并正常退出;本次真实动作有可对应的新记录。使用 exec/CI 的任务要在该运行端验一次,需要阻断策略的再单独验拒绝与允许对照。

求助时提供客户端及版本、OS/runner、交互或 exec、必要的启动参数、事件和 matcher、脱敏来源及审查状态、脚本手工结果、真实动作与新日志。不要公开完整环境、token、提示词、会话转录或带凭据的 URL;日志里的私人目录也先脱敏。无法确认派发、输出或拦截的哪一层,就明确写到哪一层,不把“没报错”当完成。

本文于 2026-10-17 核对官方 Hooks、CLI 命令参考及上述历史 issue/PR。最小探针、JSON 结构、含空格路径与脚本成功/失败分支在隔离本地目录验证;未为文章调用真实模型、改变用户 hooks/信任配置或执行真实 CI,不将离线脚本检查表述为当前所有客户端派发与策略拦截实测。

社区讨论

补充你的经验或问题

评论由 GitHub Discussions 承载;参与讨论需要登录 GitHub。