引言:Hooks 很香,但别急着“全自动”
Hooks(钩子)是 Codex 中一个极具吸引力的功能:它允许你在特定事件(如命令执行、会话开始)发生时,自动运行预设的脚本。想象一下,代码格式化、安全检查、审计日志、通知推送……这些繁琐的“杂活”都能在后台静默完成,听起来就像终于找到了一个得力的自动化助手。
然而,Hooks 与普通的提示词有着本质区别。提示词说错了,顶多得到一个不理想的回复;但 Hook 脚本写岔了,是真的会修改文件、执行命令、发起网络请求的。追求效率没错,但前提是别把自己“自动化”进坑里。
因此,本文的核心建议是:不要第一次就尝试配置“自动提交”、“自动部署”这类高风险的“大活”。正确的姿势是,从一个几乎没有副作用的“玩具” Hook 开始,把触发、信任、失败处理和禁用这四件事彻底摸清楚,再逐步增加功能。稳扎稳打,方能致远。
核心概念:分清 Hook、Skill 与 Automation
在开始配置之前,必须厘清 Codex 中几个相似的自动化概念,避免选错工具导致后续配置拧巴。
- Skill(技能):当某类任务出现时(例如用户要求“写一个函数”),按照一套可复用的流程进行处理。它更侧重于对用户意图的理解和响应。
- Automation(自动化任务):在某个时间点或按某个计划触发(例如“每周一早上”),执行预定任务。它是由时间驱动的。
- Hook(钩子):在 Codex生命周期内的某个特定事件发生时(例如
bash.command.did_finish),立即运行一个确定性的脚本。它是由系统事件驱动的。
简单区分:
- “每周一检查一次仓库更新” – 这更像Automation。
- “每次 Bash 命令执行完毕后,记录一条审计日志” – 这才是Hook。
关键原则:不要为了使用新功能,而强行把所有自动化需求都塞进 Hook。用对工具,事半功倍。
第一步:配置写在哪?保持简单,避免混乱
Codex 会从多个配置层查找 Hooks 定义,目前支持hooks.json和config.toml中的[hooks]节。最常见的四个配置位置优先级从高到低如下:
<repository>/.codex/hooks.json<repository>/.codex/config.toml~/.codex/hooks.json~/.codex/config.toml
一个重要提示:如果在同一配置层同时存在两种格式的文件,Codex 会尝试合并它们,并在启动时给出警告。更关键的是,来自不同文件且都能匹配到的 Hook 都会被加载,高优先级配置不会简单地覆盖低优先级的 Hook。
这就引出了第一个实践建议:第一次配置时,切勿贪多。只选择一种文件格式(个人推荐hooks.json,结构更清晰),并且只放入一个 Hook。否则,当出现问题(比如脚本未执行)时,你很可能需要花费大量时间去排查到底是哪一份配置在生效。
第二步:理解“信任”机制,善用/hooks命令
对于非托管的command类型 Hook,Codex 在首次运行前会要求你审查并信任。Codex 记录的是当前 Hook 定义内容的哈希值;一旦你修改了配置,哈希值改变,它就会被重新标记为“待审查”,如果没有重新信任,就会被跳过。
因此,不要只会埋头修改配置文件。你必须熟悉 Codex CLI 中的/hooks命令。这个命令可以:
- 查看所有已加载 Hook 的来源(来自哪个配置文件)。
- 进行审查(查看脚本内容)。
- 信任或禁用特定的 Hook。
很多“我明明保存了配置文件,为什么 Hook 不执行?”的问题,最终都卡在了信任状态上。请养成修改配置后,用/hooks命令检查实际状态的习惯。
顺手提醒:项目级(Repository)的 Hook 还有额外一层信任要求——项目本身的.codex/目录需要被信任。如果项目本身不受信任,那么其中的 Hook 也不会自动执行。
第三步:你的第一个 Hook——越“土”越好
首次验证,我强烈建议你写一个“没什么用”的脚本:它只做一件事——将当前时间和触发的事件名写入系统的临时文件。
#!/bin/bash# ~/.codex/hooks/echo_event.shTIMESTAMP=$(date-Is)echo“[$TIMESTAMP]Hook triggered by event:$CODEX_HOOK_EVENT”>>/tmp/codex_hook_test.log将这个脚本配置到~/.codex/hooks.json:
{“hooks”:[{“event”:“session.did_start”,“matcher”:“*”,“type”:“command”,“command”:[“bash”,“/绝对路径/to/your/.codex/hooks/echo_event.sh”]}]}这个 Hook 的优点:
- 无副作用:不读密钥、不联网、不改项目文件、不碰 Git。
- 一目了然:成功触发了吗?工作目录是什么?退出码如何?直接看
/tmp/codex_hook_test.log文件便知。
验证三步曲:
- 成功触发一次:启动新的 Codex 会话,检查日志文件,证明事件监听和脚本执行通路是畅通的。
- 故意失败一次:修改脚本,使其返回非零退出码(如
exit 1),再次触发,观察 Codex 如何显示和处理这个错误。 - 禁用一次:通过
/hooks命令禁用这个 Hook,再次触发,确认它真的不再执行。
这三步都跑通,你对 Hook 的基础生命周期就有了坚实的掌控感。此时,再考虑加入代码格式化、安全审计等复杂功能,就会从容得多。
第四步:警惕并发陷阱与配置细节
陷阱:多个 Hook 会并发执行
Codex 目前明确说明:匹配同一事件的多个commandHook 会并发启动。这意味着 Hook A 不会等待 Hook B 完成,反之亦然。
危险场景:Hook A 生成一个临时文件,Hook B 立刻去读取同一个文件。你以为配置文件中写的顺序就是执行顺序,但实际上它们可能“同时起跑”。
解决方案:如果多个操作之间有严格的先后依赖,请将它们写进同一个脚本里,或者在脚本内部实现可靠的同步机制(如文件锁)。绝对要避免多个并发 Hook 同时写入同一个输出文件,否则你将会遇到“偶尔正常、偶尔抽风”的灵异问题。
易忽略的细节
- 当前有效的 Handler 是
“type”: “command”:虽然文档中可能提到prompt或agent类型的 handler,但目前真正会执行的是command类型。如果你从旧资料中拷贝了其他类型,即使格式正确也不会运行。 - 工作目录是当前会话的
cwd:Hook 命令是从触发该事件的 Codex 会话的当前工作目录运行的。对于仓库级的 Hook,如果脚本中使用相对路径,当 Codex 从子目录启动时,路径就可能解析错误。更可靠的做法是,在脚本内使用 Git 根目录来解析路径。
第五步:系统化的排错流程
当 Hook 不执行时,不要盲目给 Codex 或脚本提升权限。遵循从简到繁的排查顺序:
- 独立测试脚本:在终端中,模拟 Hook 的环境(设置相关环境变量如
$CODEX_HOOK_EVENT),手动运行你的脚本。确认路径、执行权限、工作目录、环境变量都正确,并且退出码符合预期。 - 检查 Codex 状态:运行
/hooks命令。确认:- 你的 Hook 是否出现在列表中(事件名
event、匹配器matcher是否正确)。 - 它的来源(Source)是否是你修改的配置文件。
- 它的信任状态(Trusted)是否为
Yes。 - 它是否被禁用(Disabled)。
- 你的 Hook 是否出现在列表中(事件名
- 检查项目信任:确认项目本身的
.codex/目录是否已被信任。 - 隔离测试:临时禁用其他匹配同一事件的 Hook,只保留一个进行测试,排除干扰。
- 观察失败行为:故意让脚本失败(如
exit 1),观察 Codex 的错误提示和回滚行为。
这套“老土”的流程非常有效:先证明**脚本本身