用 `codex exec` 把任务写成可重复执行的命令
交互式用 Codex(OpenAI Codex)有个绕不开的问题:这次跑通了,下次还得从头把上下文、模式、目录再敲一遍。同事问你「你上回怎么让它做的」,你只能凭印象复述。真正能沉淀下来的不是对话,是一条能贴进 README、能塞进 npm script、能让别人原样复制的命令。
Codex CLI 里干这件事的入口是 codex exec(别名 e),help 里的一句话说明是 “Run Codex non-interactively”。下面这些选项释义都取自 codex-cli 0.147.0(Windows 11)上 codex exec --help 与 codex --help 的原文。
一、命令怎么写
先说一件写脚本时最容易踩的形式问题:选项分两类,位置不一样。codex --help 列的是顶层选项(-C / -s / -a / -c / --add-dir 这些),按用法行 codex [OPTIONS] <COMMAND> [ARGS] 的形态,它们写在子命令 exec 之前;codex exec --help 列的才是 exec 独有的(--skip-git-repo-check / --color / --json / -o / --output-schema / --ephemeral / --ignore-user-config / --ignore-rules),写在 exec 之后。本机 0.147.0 上官方给的 -c 用法示例也是这个形态,例如 codex -c 'features=[unclosed' doctor --summary——-c 在子命令前面。
下面这条骨架就按这个形态写,Git Bash / macOS / Linux 通用:
codex \
-C <你的仓库目录> \
-s read-only \
-a never \
exec \
--skip-git-repo-check \
--color never \
-o ./codex-out/last-message.md \
"把 src 目录下所有对外导出的函数整理成一张表,只输出表格,不要改任何文件"
Windows PowerShell 下换行符不同,续行用反引号:
codex `
-C <你的仓库目录> `
-s read-only `
-a never `
exec `
--skip-git-repo-check `
--color never `
-o .\codex-out\last-message.md `
"把 src 目录下所有对外导出的函数整理成一张表,只输出表格,不要改任何文件"
选项位置未逐项实测,以 codex --help 与 codex exec --help 的当次输出为准;把命令抄进脚本前,建议先用这两条 help 核一遍你手上这个版本的分组。
逐个说为什么这几项在这里:
-C, --cd <DIR>(顶层):显式指定 agent 的工作根目录。写脚本时最怕的就是「在哪个目录下跑」这件事靠调用者的当前路径决定,把它钉死,别人从任何位置执行结果都一样。-s, --sandbox <SANDBOX_MODE>(顶层):取值只有三个,read-only/workspace-write/danger-full-access。这条命令只是让它读代码出表格,不需要写文件,就给最小的read-only——官方对这一档的原文是 “The agent can inspect files, but it can’t edit files or run commands without approval.”-a, --ask-for-approval <APPROVAL_POLICY>(顶层):三档untrusted/on-request/never。非交互场景没人在终端前面按回车,所以选never(官方释义:从不询问,执行失败直接回传给模型)。这里有个高频误解要说清楚:never改变的只是「要不要问你」,它并不放开权限,沙箱边界仍然由-s决定。真正撤边界的是danger-full-access或那个 help 里被写成 “EXTREMELY DANGEROUS” 的--dangerously-bypass-approvals-and-sandbox。把-a never当成「全放开」而不配-s,是踩坑的起点。--skip-git-repo-check(exec 独有):允许在非 Git 仓库里运行。如果你的目标目录本来就是仓库,这项可以不加;但脚本要发给别人用、不确定对方的目录形态时,加上能省一次莫名其妙的失败。--color <COLOR>(exec 独有):always/never/auto,默认auto。重定向进日志文件时选never,否则日志里会混进控制字符,后面 grep 和 diff 都不好受。-o, --output-last-message <FILE>(exec 独有):把 agent 的最后一条消息写到文件。这是「让下一步能接上」的关键,下一节细讲。- 位置参数
[PROMPT]:直接作为首轮指令带进去。提示词本身建议写得像验收标准,而不是像聊天——明确说「只输出什么」「不要动什么」。
让上游数据流进来
codex exec 的位置参数还有两种更适合脚本的用法,这两条行为是 help 里明确写了的:
不给参数、或者给一个 -,prompt 从 stdin 读:
cat ./prompts/review-checklist.md | codex exec -
而如果 stdin 是管道、同时又给了 prompt,stdin 的内容会作为一个 <stdin> 块追加到 prompt 后面:
git diff --staged | codex -s read-only -a never exec "对照下面这段 diff,逐条检查有没有漏处理的边界条件"
这个组合很实用:提示词是固定资产写死在脚本里,变动的部分(diff、日志、报错栈)由管道喂进去。你不需要每次去拼一个巨大的字符串。
需要写文件的时候
只有确实要改代码时才升到 workspace-write,并且用 --add-dir 精确追加主工作区之外的可写目录,而不是直接跳到 danger-full-access:
codex \
-C <你的仓库目录> \
-s workspace-write \
--add-dir <额外需要写的目录> \
-a never \
exec \
-o ./codex-out/last-message.md \
"只修改 tests 目录下的用例,其余文件不要动"
--add-dir 和 -s 一样是顶层选项,所以同样落在 exec 前面。
另外还有一个 --approve-for-me:审批请求走自动复核,并使用 workspace-write 沙箱。它和手写 -s workspace-write 是两条不同的路径,选哪条取决于你要不要那层自动复核,别两个都堆上去然后猜谁生效。
把「可重复」做到底
一条命令要在别人机器上跑出同样的行为,最大的变量是对方的本地配置。codex exec 有三个专门用来隔离环境的开关:
| 选项 | help 原文含义 | 什么时候用 |
|---|---|---|
--ignore-user-config | 不加载 $CODEX_HOME/config.toml;auth 仍然使用 CODEX_HOME | 想让命令的行为完全由命令行决定时 |
--ignore-rules | 不加载用户或项目的 execpolicy .rules 文件 | 排查「为什么他那儿被拦住了」时用来做对照 |
--ephemeral | 不把会话文件落盘 | 批量跑、不想让 sessions 目录无限膨胀时 |
注意 --ignore-user-config 那句「auth 仍然使用 CODEX_HOME」——它隔离的是配置不是登录态,别指望靠它来切账号。
需要临时改某个配置项时,用顶层的 -c, --config <key=value>,点号路径表示嵌套,value 按 TOML 解析:
codex \
-C <你的仓库目录> \
-s read-only -a never \
-c model_reasoning_effort=low \
-c hide_agent_reasoning=true \
exec \
-o ./codex-out/last-message.md \
"..."
model_reasoning_effort 的取值官方给的是 minimal / low / medium / high / xhigh;hide_agent_reasoning 的官方说明是「在 TUI 与 codex exec 输出里抑制推理内容」——脚本里日志越干净越好,这一项就是为这个场景准备的。以上为按官方文档键位组合的示例,未逐项实测,以官方文档为准。
二、产出物长什么样
这是非交互模式最值钱的部分,也是最容易被想当然的部分。就 help 明确写了的来说:
--json:事件以 JSONL 打到 stdout。也就是一行一个 JSON 对象的流,不是一整个 JSON 文档,所以要按行解析,别整个文件丢给json.loads。具体有哪些事件类型、字段叫什么,help 里没写,本文也不编——你第一次接的时候自己打开文件看一眼字段名再写解析器,这一步省不掉。-o, --output-last-message <FILE>:把 agent 的最后一条消息写到你指定的文件。要强调「最后一条」:中间过程不在里面。按 help 的释义,这两项作用于不同通道——--json打到 stdout,-o写进你指定的文件;但两者同时给会不会各自照常工作,我们没有实测,要用请以codex exec --help与你自己那次的实际输出为准,别把它当成既定结论写进团队脚本。--output-schema <FILE>:接一个 JSON Schema 文件的路径,用来描述模型最终回复的结构。如果下游是程序而不是人,这条比在提示词里写「请输出 JSON」靠谱得多。
所以更稳的做法是先想清楚这一趟要哪一种产出,再挑一个。要过程流就走 --json,把 stdout 重定向进文件:
mkdir -p ./codex-out
codex \
-C <你的仓库目录> \
-s read-only -a never \
exec --json --color never \
"..." \
> ./codex-out/events.jsonl
要结论就走前面那条带 -o 的骨架,拿到的是一份可以直接贴 PR 评论、拼进周报、或者再喂给下一条命令的文件。注意 -o 只负责写文件,指定的目录不存在时要自己先 mkdir。
顺带说一句会话本身。codex exec 有 resume 子命令(按 id 续,或 --last 续最近一次),会话文件默认落在 ~/.codex/sessions/ 下按年份分子目录。这意味着批量跑是有磁盘代价的:在本机 codex-cli 0.147.0(Windows 11)上,codex doctor --summary 的 Notes 区就报了 rollouts 占 405 个活动文件、3.07 GB。你要是打算让脚本一天跑几十次,--ephemeral 要么现在加,要么以后清盘的时候加。
三、怎么验收
命令写完别急着交给同事,按这个顺序自己过一遍。
第一步,确认配置真的加载成功了。 这是本机实测得到的一条很实用的结论:在 codex-cli 0.147.0(Windows 11)上执行 codex -c 'features=[unclosed' doctor --summary,命令没有崩溃退出,doctor 照常跑完,但结果里出现了这一行:
✗ config config could not be loaded - Fix the reported config error, then rerun codex doctor.
也就是说,配置写坏了不一定会让你的命令当场失败,它可能只是悄悄没生效。所以「改完配置行为没变」的第一个动作应该是:
codex doctor --summary
去看 Configuration 分组下的 config、auth、sandbox 三行。本机这台的 sandbox 行显示的是 restricted fs + restricted network · approval OnRequest,能直接印证当前生效的边界。状态符号有四种:✓ ok、○ idle、⚠ notes/warn、✗ fail,结尾还有一行形如 17 ok · 1 idle · 1 notes · 0 warn · 0 fail 的统计。要把诊断结果贴给别人看,用 codex doctor --json——官方对它的说明是 “Emit a redacted machine-readable report”,是脱敏的。
第二步,别指望 --strict-config 兜住一切。 --strict-config 的作用是 config.toml 里出现本版本不认识的字段时直接报错退出,听起来是拼写错误的保险丝。但本机实测:在 codex-cli 0.147.0(Windows 11)上执行 codex -c model_reasoning_effortt=high --strict-config exec --help(注意那个多打的 t),它正常打印了 help,没有报未知字段错误。结论是校验发生在真正加载配置去跑会话的时候,--help 这类不进入会话的路径不触发。所以拿 --help 做冒烟测试是测不出配置拼写错的。
第三步,检查引号有没有被 shell 吃掉。 -c 的 value 按 TOML 解析,help 里明写了解析失败则按字面字符串处理。按这条规则往下推:如果引号被 shell 处理掉、传进去的东西不再是合法 TOML,那它就会被当成字面字符串收下,而不是当场报错告诉你写错了——这一步我们没有实测,但既然规则是「不报错,降级成字符串」,就不能指望终端替你把关。判断方法还是回到第一步的 doctor,看配置那一行到底是什么状态。
第四步,看产出物本身。 -o 指定的文件到底写出来没有(目录不存在时先 mkdir);--json 的输出逐行是不是合法 JSON;--color never 有没有真的把控制字符去掉。
第五步,把版本号记进脚本注释。 本机采集时遇到过一件事:同一台机器,先执行 codex --version 得到 codex-cli 0.131.0,十几分钟后再执行同一条命令得到 codex-cli 0.147.0,而 which -a codex 全程只有一个可执行文件。Codex 有自更新能力,配置里 check_for_update_on_startup 默认就是 true。所以排查任何「昨天还好好的」问题,都必须以当次 codex --version 的实时输出为准,不要用记忆里的版本号。脚本里顺手打一行版本,将来排查省一半事。
把命令交出去之前,这四处值得逐一回看(这是命令行通用的检查清单,不是本机 codex exec 的实测统计):引号有没有被 shell 改写;-C 与相对路径的 -o 一起用时输出文件落到了哪个目录;-s 给的档位够不够这个任务真正做完;--color never 有没有漏加。
四、什么情况不适用
- 需要人在旁边一步步确认的活儿别用它。
-a never的官方释义是执行失败直接回传给模型,没有人工介入点。改数据库、动生产配置、执行不可逆操作这类,老老实实交互式跑。 - 别为了「跑得顺」直接上
danger-full-access或--dangerously-bypass-approvals-and-sandbox。 后者 help 原文写的是仅用于本身已经在外部沙箱里的环境。在自己的开发机上把它写进一条要给全组复制的命令里,风险是整组的。 - 对输出稳定性有硬要求的场景要人工兜底。 我们没有实测过同一条
codex exec多次运行的输出一致性,也就不做任何承诺。要接程序,用--output-schema把结构约束住,然后在下游做校验和失败重试——把「模型这次听话了」当成前提是不行的。 - 跨平台照搬要重新验。 官方对
features.unified_exec标注的默认值是true但 Windows 除外;本机 codex-cli 0.147.0(Windows 11,2026-08-09)上codex features list给出的生效值确实是false。同理,沙箱实现在 macOS 是 Seatbelt、Linux/WSL2 靠bubblewrap、Windows 是原生受限 token,是三套东西。同事在 mac 上跑通的命令搬到 Windows,至少要重跑一遍 doctor。 - 一次性的探索别硬写成脚本。 还没想清楚要什么的时候,交互式试比反复改命令行快。等到同一件事你手动做了第三遍,再固化成
codex exec,那时候提示词也磨得差不多了。
相关阅读
- 把
codex exec --json接进自己的流水线:命令怎么写、产出怎么接、怎么验收 - 把回复变成脚本能解析的 JSON:
codex exec --output-schema实战 - 在 CI 里跑
codex exec:非仓库、免配置与超时这三件事 - Codex 的六个使用面:一张图看懂该用哪个
本文依据 Codex 官方文档(learn.chatgpt.com/docs/ 的《Non-interactive mode》《Configuration Reference》《Sandbox》页面)整理,核对日 2026-08-09;文中标注「本机实测」的部分基于 codex-cli 0.147.0 / Windows 11 环境下的只读命令输出。产品功能、模型与价格以官方最新说明为准。