用 `notify` 让 Codex 跑完主动叫你:配置写法、通知脚本与验收清单
把一个稍微像样点的活儿交给 Codex(OpenAI Codex)之后,最难受的不是它慢,是你不知道它什么时候完事。切去看别的窗口吧,怕它中途弹审批卡在那;一直盯着终端吧,那这半小时基本就废了。Codex CLI 的配置里有个 notify 键就是干这个的——让它跑到该通知你的时候,主动去叫一个你自己写的命令。
下面这套做法我按「配置怎么写 → 产出物长什么样 → 怎么验收 → 什么情况别这么干」的顺序过一遍。
先把 notify 的事实边界划清楚
官方《Configuration Reference》页里,notify 这一条的说明只有一句话:它是一个数组,是「通知时以 JSON 调用的命令」。
就这么多。这一句话里能确认的事实有两条:
- 值的类型是数组,不是一个字符串命令行。也就是说你得把可执行文件和参数拆开写成数组元素,而不是写成一整行让 shell 去解析。
- 调用时会带 JSON。
而这一句话里没有说的东西,我这篇也不会替它补:它在哪些时机触发(只在整轮跑完?还是别的节点也算?)、JSON 里有哪些字段、JSON 是走命令行参数还是走标准输入,Configuration Reference 这一页都没有展开。官方另有一页专门叫《Notifications》,但我这次只拿到了页名、没取到正文,所以关于触发时机和 JSON 结构的细节,请以那一页为准。
我知道这听着挺扫兴,但正因为字段名不明,下面的脚本第一版才必须那么写——先把 Codex 递给你的东西原样存下来看一眼,比照着猜出来的字段名写解析要稳得多。猜错字段名的后果不是报错,是通知脚本安安静静地什么都不弹,你还以为 notify 没生效。
配置怎么写
notify 写在 $CODEX_HOME/config.toml 里,默认位置就是 ~/.codex/config.toml。
类 Unix(macOS / Linux):
notify = ["node", "/opt/codex-notify/notify.js"]
Windows 上有两处不一样,都容易踩:
notify = ["node", "<你的用户目录>\\.codex\\notify.js"]
一是路径分隔符。TOML 的基本字符串里 \ 是转义字符,所以 \.codex\ 这种写法会被吃掉,要么像上面那样写双反斜杠,要么用 TOML 的字面量字符串(单引号)。二是别指望它帮你展开 ~——把绝对路径写全,或者干脆把脚本放到一个不带中文、不带空格的目录里,省得后面排查时怀疑人生。
数组第一个元素是可执行文件,后面每个元素是一个独立参数,不要自己往里塞引号。想让脚本区分是哪台机器、哪个项目发来的,可以固定追加一个自己的标记参数:
notify = ["node", "<你的用户目录>\\.codex\\notify.js", "--tag", "work-laptop"]
不想直接改配置文件先试一把的话,用顶层的 -c 覆盖。-c 的官方说明写得很清楚:值按 TOML 解析,解析失败就按字面字符串处理,官方自己给的例子里就有数组形式(-c 'sandbox_permissions=["disk-full-read-access"]')。所以临时覆盖长这样:
codex -c 'notify=["node","/opt/codex-notify/notify.js"]'
「解析失败按字面字符串处理」这句要当心:如果你在 Windows 的 cmd 里被引号坑了,notify 很可能被当成一个字符串而不是数组塞进去,结果就是不生效且不报错。用 PowerShell 或 Git Bash 写这条命令,比在 cmd 里跟引号搏斗省事。
以上配置片段是按官方文档的键位组合出来的示例,未逐项实测,以官方文档为准。
通知脚本第一版:只负责把收到的东西记下来
// notify.js —— 第一版不解析、不弹窗,只做取证
const fs = require("fs");
const os = require("os");
const path = require("path");
const outFile = path.join(os.homedir(), ".codex", "notify-capture.log");
let stdinBuf = "";
let written = false;
function dump() {
if (written) return;
written = true;
const record = {
at: new Date().toISOString(),
argv: process.argv.slice(2),
stdin: stdinBuf,
};
fs.appendFileSync(outFile, JSON.stringify(record) + os.EOL, "utf8");
process.exit(0);
}
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => (stdinBuf += chunk));
process.stdin.on("end", dump);
setTimeout(dump, 300); // 万一没有标准输入,别把进程挂在这儿
每个设计都有理由:argv 和 stdin 两头都记,是因为文档没说 JSON 从哪条路进来,两头都收就不用赌;appendFileSync 是追加而不是覆盖,多跑几次能看出触发规律;那个 300 毫秒的兜底定时器是为了防止管道那头没人写数据、进程一直不退出——一个永远不退出的通知脚本会比不通知更烦人。
跑几次之后打开 notify-capture.log,你就能看到真实的调用形态:JSON 在哪条通道、有哪些键、每次触发时机是什么。到这一步再写解析,才是有依据的解析。
第二版就简单了:把你确认存在的字段拼成一句人话,然后交给你自己已有的那条通知通道——终端响铃、桌面通知工具、企业微信/飞书群机器人、发条邮件,都行。Codex 这边只负责把命令叫起来,通知长什么样是你的自由。这个分工挺好,意味着你不需要等官方支持某个通知渠道。
更轻的做法:TUI 自带的三个开关
如果你只是想在 Codex 需要你的时候听个响,不必写脚本。配置参考里 TUI 一节有三个直接相关的键:
| 键 | 默认 | 取值 |
|---|---|---|
tui.notifications | — | — |
tui.notification_method | auto | osc9 / bel |
tui.notification_condition | unfocused | always |
先说这张表怎么读:官方在这一节只给了键名、默认值和取值枚举,没有逐条的说明文字。tui.notifications 那一行两列都是「—」,不是我漏填,是官方连它的类型和默认值都没给——所以要不要显式写这个键、写成什么值,你得自己试,别照着别人的博客抄。
tui.notification_condition 默认 unfocused 这个默认值定得很实在:你人就在这个窗口盯着看的时候不打扰你,切走了才响。如果你是那种开着终端却在另一个屏幕上干活的人,改成 always 更合用。
tui.notification_method 的两个取值对应两种不同的终端通知机制,具体哪个在你机器上能用,取决于终端模拟器;官方只给了枚举值,默认 auto 是自动选择。所以合理的用法是先留着 auto,只有在实际听不到响、也看不到提示的时候,才手动换成 bel 或 osc9 各试一遍,看哪个有反应——这属于试出来的结论,不是能从文档推出来的结论。
顺带一个不属于通知、但和「跑长任务」强相关的键:features.prevent_idle_sleep,默认 false,官方标注为 experimental。它管的是活跃回合内阻止机器休眠。笔记本合盖或者到点息屏把任务掐了,通知自然也就无从谈起。这个开关目前是实验阶段,别当稳定功能依赖,但知道有这么个东西,排查「任务莫名其妙没跑完」时能少走弯路。
另外 tui.terminal_title 默认是 ["spinner", "project"]——终端标题栏里带着转圈标识和项目名。开一排标签页时,扫一眼标题栏就知道哪个还在转,成本比配脚本低得多。
非交互场景:别用 notify,用 exec 的退出
如果这个长任务本来就是脚本调起来的,那绕开 notify 更省事:codex exec 是非交互执行,进程退出就是任务结束,你在它后面串一条通知命令就完了,触发时机完全由你掌握。
codex exec --json --color never -o last-message.txt "把 src 下的 TODO 汇总成清单" \
&& node /opt/codex-notify/notify.js --tag done \
|| node /opt/codex-notify/notify.js --tag failed
这几个选项都是 codex exec 的实测原文项:--json 让事件以 JSONL 打到 stdout,一行一个事件,方便你自己接下游处理;-o, --output-last-message <FILE> 把 agent 的最后一条消息写到文件,通知脚本读这个文件就能在消息里带上结论摘要;--color never 是防止 ANSI 颜色码混进你要转发的文本里(--color 取值 always / never / auto,默认 auto)。
需要留意 codex exec 的位置参数规则:不给参数或给 - 时从标准输入读;如果标准输入是管道而你又给了 prompt,标准输入的内容会作为 <stdin> 块追加上去。批处理脚本里 echo ... | 这类写法很常见,容易在不知不觉中把额外内容喂进去。
无人值守还有个前提得说清楚:任务能不能跑到「结束」,取决于中途会不会停下来等审批。-a, --ask-for-approval 的 never 是「从不询问,执行失败直接回传给模型」——注意这句官方释义,never 不等于给它更大权限,它只是不问你,该失败还是失败,只不过失败信息回给模型自己处置。想让它跑完再叫你,就得接受这个取舍。
怎么验收
按顺序检查这几处,出错基本都在前三步:
-
脚本单独能跑吗。 先手工执行一次
node <脚本路径> --tag test,确认能生成日志文件。Codex 只负责调用它,脚本自己的 bug 不会有人替你报。 -
配置到底加载成功没有。 这一步最值钱。在 codex-cli 0.147.0(Windows 11)上实测过一个场景:故意用
-c 'features=[unclosed'传一段语法不合法的 TOML,命令没有崩溃退出,codex doctor --summary照常跑完,但结果里出现了这么一行:✗ config config could not be loaded - Fix the reported config error, then rerun codex doctor.也就是说,配置坏掉时 Codex 不会拦着你,只会在 doctor 里明确告诉你没加载成功。所以「我明明配了
notify怎么一点动静没有」,第一件事就是跑codex doctor --summary看这一行是 ✓ 还是 ✗。 -
别指望
--strict-config帮你抓拼写错误。 它的作用是配置里出现本版本不认识的字段时报错退出。但在 codex-cli 0.147.0(Windows 11)上实测:codex -c model_reasoning_effortt=high --strict-config exec --help正常打印了 help,没有报未知字段错误。说明这个校验发生在真正加载配置去跑会话的时候,--help这类不进入会话的路径不触发。拿--help验配置,等于没验。 -
看日志目录。
log_dir默认是$CODEX_HOME/log,在 codex-cli 0.147.0(Windows 11)上实测该目录下有codex-tui.log。通知没响而 doctor 又是绿的,去这里翻。 -
最后再确认取证日志。
notify-capture.log里有没有新增行,决定了问题在「Codex 没调你的脚本」还是「脚本被调了但你的通知渠道没送达」。这两件事的排查方向完全不同,别混在一起猜。
什么情况不适用
别指望它替你守审批。 审批弹窗是要人回答的,通知能不能覆盖到这个时机,取决于《Notifications》页的实际定义,我这次没有依据。真要无人值守,得先把审批策略这条路想明白,而不是指望通知补位。
桌面应用、Codex cloud、IDE 扩展这三个面,本文的做法一概不适用。 notify 是 CLI 配置文件里的键,上述三个面我完全没有实测,它们各自的通知怎么配,官方文档给的做法为准。顺带一条官方排查条目值得记住:功能在 CLI 有、桌面应用没有,往往是两个面的版本不同,官方给的做法是分别查版本——CLI 用 codex --version。这一点在通知这类逐版本变化的功能上尤其常见。
通知不等于验收。 收到「跑完了」的提示,只说明进程结束了,不代表改动是对的。codex apply 的官方说明是把 agent 产出的最新 diff 以 git apply 打到本地工作树——什么时候 apply,仍然该是你看过之后的决定。
版本会漂。 本机采集时有过一次真实经历:同一台机器,开头 codex --version 是 codex-cli 0.131.0,十几分钟后再执行同一条命令变成了 codex-cli 0.147.0,而 which -a codex 全程只有一个可执行文件。配置里 check_for_update_on_startup 默认就是 true。所以排查通知相关问题时,以当次 codex --version 的实时输出为准,别拿记忆里的版本号跟别人对账。
相关阅读
- Codex shell 环境变量策略:哪些变量会被带进子进程
- Codex 的 AGENTS.md 该写什么、写多长、放在哪一层
- Codex CLI 用
--add-dir让 agent 同时读写两个目录 - Codex 的六个使用面:一张图看懂该用哪个
本文依据 Codex 官方文档(learn.chatgpt.com/docs/ 的《Configuration Reference》《Notifications》《Non-interactive mode》《Troubleshooting》页面)整理,核对日 2026-08-09;文中标注「本机实测」的部分基于 codex-cli 0.147.0 / Windows 11 环境下的只读命令输出。产品功能、模型与价格以官方最新说明为准。桌面应用与云端部分为官方文档口径,非本机实测。