在 CI 里跑 `codex exec`:非仓库、免配置与超时这三件事
Codex(OpenAI Codex)的 codex exec 子命令,官方给的一句话说明是 “Run Codex non-interactively”,别名 e。听上去只是”把交互式换成一条命令”,但真往流水线里塞,卡住人的往往不是提示词,而是三件很土的事:CI 的工作目录不是 Git 仓库、runner 上没有你本机那份 config.toml、以及这条命令到底会跑多久、超时归谁管。
这三件事在官方选项表里都有对应,只是分散在不同位置。下面按”命令怎么写 → 产出物在哪 → 怎么验收 → 什么时候别用”的顺序过一遍。
一、先把命令写出来
先看骨架。Codex CLI 的用法是 codex [OPTIONS] <COMMAND> [ARGS],沙箱、审批、工作目录这些是顶层选项,要写在 exec 前面;--skip-git-repo-check、--json 这些是 exec 独有的,写在后面。照 usage 的位置摆,别把两类选项混着写。
Linux / macOS runner(POSIX shell):
printenv OPENAI_API_KEY | codex login --with-api-key
codex \
-s read-only \
-a never \
-C "$PWD" \
-c hide_agent_reasoning=true \
exec \
--skip-git-repo-check \
--ignore-user-config \
--ephemeral \
--color never \
--json \
-o codex-last-message.txt \
"把 docs/ 目录下的改动整理成一段不超过 20 行的发布说明草稿"
逐个说为什么在这:
printenv OPENAI_API_KEY | codex login --with-api-key是官方给的 API key 登录写法,key 从 stdin 读,不会出现在命令行参数里(参数会被很多 CI 的日志和进程列表看到)。另一种认证是 ChatGPT 登录,走浏览器,在无浏览器的 runner 上不现实;官方对远程机器给的首选是codex login --device-auth(设备码登录,官方标注 beta),需要人工输一次性验证码——这在无人值守的流水线里同样不成立。所以 CI 场景基本只剩 API key 这条路。密钥一律从 CI 的密钥管理里注入,别落到仓库。-s read-only:沙箱模式,取值只有read-only/workspace-write/danger-full-access三个。CI 里第一版建议先read-only跑通链路,确认它不改文件;确实要它落盘改动再换workspace-write。-a never:审批策略。官方释义是”从不询问,执行失败直接回传给模型”。注意never不等于放开权限——它只是不弹审批,该被沙箱挡住的照样挡。这个词容易理解反。非交互模式下没人能点确认,所以审批策略必须显式给。-C "$PWD":显式指定 agent 的工作根目录,别依赖 runner 的隐式当前目录。-c hide_agent_reasoning=true:这个配置键的官方说明就是”在 TUI 与codex exec输出里抑制推理内容”,CI 日志能干净不少。--ephemeral:不把会话文件落盘。runner 大多是一次性的,落了也没人看;更实际的原因是磁盘——本机在 codex-cli 0.147.0(Windows 11)上跑codex doctor --summary,Notes 区直接提示 rollouts 占了 3.07 GB。这个体积在长期复用的自托管 runner 上会变成运维问题。--color never:--color取值always/never/auto,默认auto。CI 日志里的 ANSI 转义序列会把后续文本分析搞乱,显式关掉。
二、“非仓库”:--skip-git-repo-check
codex exec 有一个独有选项 --skip-git-repo-check,作用是允许在非 Git 仓库里运行。换句话说,默认情况下它会检查当前目录是不是 Git 仓库。
CI 里踩到这条的情况比想象中多:把产物解压到临时目录再让 Codex 读一遍、容器里只挂了 dist/、或者流水线做了浅克隆之后又把 .git 清掉减体积。这些目录都不是仓库,不加这个开关就会被拦下。
反过来,如果你的作业本来就在完整仓库里跑,不建议无脑加。这个检查某种意义上是道保险:它能在”工作目录没切对”的时候提前失败,而不是让 Codex 在一个空目录里认真地思考一遍。
三、“免配置”:让 runner 上的结果可复现
本机跑得好好的,CI 上行为不一样,八成是配置差异。相关的有三个开关,作用范围各不相同:
| 选项 | 作用 | 注意 |
|---|---|---|
--ignore-user-config | 不加载 $CODEX_HOME/config.toml | auth 仍然使用 CODEX_HOME,登录凭据不受影响 |
--ignore-rules | 不加载用户或项目的 execpolicy .rules 文件 | 与配置文件是两套东西 |
--strict-config | config.toml 里出现本版本不认识的字段时直接报错退出 | 顶层选项,写在 exec 前 |
这里有个容易忽略的点:--ignore-user-config 只关配置,不关认证——官方说明明确写了 auth 仍然走 CODEX_HOME。所以你不会因为加了它就掉登录,但也别指望它能隔离掉凭据。
于是 CI 里有两条路线,选哪条取决于你要不要用仓库里那份配置:
路线 A:完全不吃本机配置。 加 --ignore-user-config,然后把需要的键用 -c 一条条显式给出来。好处是 runner 换了、镜像重建了、别人的机器上跑,行为都一样。代价是每个键都得自己写。
路线 B:吃配置,但用 --strict-config 兜拼写错误。 适合团队把 config.toml 纳入版本管理的情形。
-c 的写法有个坑必须说清楚:-c key=value 里的 value 按 TOML 解析,解析失败则按字面字符串处理,点号路径表示嵌套。官方给的三个例子是 -c model="o3"、-c 'sandbox_permissions=["disk-full-read-access"]'、-c shell_environment_policy.inherit=all。留意第二个例子整段被单引号包住——数组、表这类值里带引号和方括号,shell 会先吃一道,不整体加引号必然传歪。而”解析失败按字面字符串”这个设计意味着传错了不一定报错,可能悄悄变成一个字符串值,这比报错更难查。
--strict-config 也有边界。本机在 codex-cli 0.147.0(Windows 11)上执行 codex -c model_reasoning_effortt=high --strict-config exec --help(键名故意多打一个 t),结果正常打印了 help,没有报未知字段错误。说明它的校验发生在真正加载配置去跑会话时,--help 这类不进入会话的路径不触发。所以别把”CI 里加了 --strict-config 就能拦住所有拼写错误”当成结论。
Windows runner 上还有两处差异要留意:配置键 features.unified_exec 官方标注默认 true 但 Windows 除外,本机 codex features list 在 0.147.0 上实测该项生效值为 false,两边对得上;另外 windows.sandbox 这个键的取值是 unelevated 或 elevated,Windows 的原生沙箱机制本来就和 Linux 不是一套东西。跨平台矩阵跑同一条命令时,别默认两边行为一致。
四、“超时”:谁来管这条命令跑多久
先说结论:本机在 codex-cli 0.147.0(Windows 11)上看 codex exec --help,独有选项里没有一个控制整体运行时长的开关。也就是说,“这条命令最多跑 15 分钟”这件事得由 CI 平台的作业超时、或者外部的 timeout 之类工具来兜,不是 Codex 的功能。
Codex 侧能配的是几个局部超时,都在配置键里,且各管各的:
| 配置键 | 默认值 | 管什么 |
|---|---|---|
mcp_servers.<id>.startup_timeout_sec | 10 | MCP server 启动超时 |
mcp_servers.<id>.tool_timeout_sec | 60 | MCP 工具调用超时 |
background_terminal_max_timeout | 300000 | 后台终端(毫秒,即 5 分钟) |
model_providers.<id>.stream_idle_timeout_ms | 300000 | 自定义提供方的流空闲超时 |
model_providers.<id>.request_max_retries | 4 | 自定义提供方的请求重试次数 |
MCP 那两个值得单独盯一下。启动超时的默认值只有 10 秒——这是官方文档给的默认值,我们没有在任何 CI/runner 上实际跑过 MCP server,下面这段是按默认值做的推断而不是实测:冷缓存的 runner 上装依赖、拉镜像、启进程,10 秒很容易不够。更麻烦的是 mcp_servers.<id>.required:启用的服务器初始化失败会导致启动失败,于是表现成”整条流水线在还没开始干活的时候就红了”。要么把 startup_timeout_sec 调大,要么在 CI 里干脆用 --ignore-user-config 把这些 server 一起排除掉——后者更干脆,前提是你的任务确实不依赖 MCP。
(上表把多个键放在一起是为了对照,实际写进 config.toml 时按官方文档的表结构分节写;以上为按官方文档键位组合的示例,未逐项实测,以官方文档为准。)
还有一个和”跑多久”无关但会咬人的事实:check_for_update_on_startup 默认 true,Codex 具备自更新能力。本机采集当天,开头执行 codex --version 得到 codex-cli 0.131.0,十几分钟后同一台机器同一条命令得到 0.147.0,which -a codex 全程只有一个可执行文件。放到 CI 语境里就是:你的流水线用的版本可能和上周不是同一个。排查任何版本相关问题,都要以当次 codex --version 的实时输出为准,别用记忆里的版本号;需要可复现的场合,把版本号打进日志的第一行。
五、产出物长什么样
codex exec 有两个和产出直接相关的选项,加上一个描述结构的:
--json:事件以 JSONL 打到 stdout,一行一个事件。CI 里可以边跑边收,也可以重定向到文件留档。具体有哪些事件类型、字段叫什么,得看你当次的实际输出——本文不列字段名,因为我们没有可引用的依据,照抄别人博客里的字段名是给自己挖坑。-o, --output-last-message <FILE>:把 agent 的最后一条消息写到文件。这是最实用的一个:流水线的下一步(贴评论、发通知、存产物)只想要结论,不想解析事件流,那就读这个文件。--output-schema <FILE>:接一个 JSON Schema 文件的路径,用来描述模型最终回复的结构。想让下游程序化消费结果,比事后用正则抠文本靠谱。
另外提示词的传入方式也值得单说:位置参数 [PROMPT] 可以直接写在命令行;不给参数或给 - 时从 stdin 读;如果 stdin 是管道且同时给了 prompt,stdin 会作为 <stdin> 块追加。这条在 CI 里很好用——固定的指令写死在 pipeline 里当 prompt,变动的内容(diff、日志、构建报错)从管道灌进去,两者会合并而不是互相覆盖。
六、怎么验收
在跑真任务之前,先加一段预检。这几处按顺序查,出问题的位置基本跑不出这个范围:
codex --version
codex login status
codex doctor --summary
codex --version:把版本打进日志第一行,上面说过原因。codex login status:确认认证真的生效了。本机在 codex-cli 0.147.0(Windows 11)上执行这条命令,输出是一行Logged in using ChatGPT;CI 里用 API key 登录的话输出会不同,重点是它得给出一个明确的已登录状态,而不是报错。登录失败的诊断信息会写在配置的日志目录里的codex-login.log。如果公司网络有 TLS 代理或私有根 CA,官方给的做法是登录前设置环境变量CODEX_CA_CERTIFICATE。codex doctor --summary:重点看 Configuration 分组里的config那一行。本机在 0.147.0 上故意传了一段语法不合法的 TOML(codex -c 'features=[unclosed' doctor --summary),命令没有崩溃退出,doctor 照常跑完,但输出里出现了这么一行:
✗ config config could not be loaded - Fix the reported config error, then rerun codex doctor.
这是”改完配置没生效”最快的判定手段。它同时也说明一件事:配置坏掉不一定让命令失败,CI 里如果只看退出码,可能一路带着没加载成功的配置跑到底。doctor 的结尾统计行是 N ok · N idle · N notes · N warn · N fail 的格式,状态符号有 ✓(ok)、○(idle)、⚠(notes/warn)、✗(fail)四种,把这一行捞出来做断言比人眼看日志靠谱。
codex doctor --json 的官方说明是 “Emit a redacted machine-readable report”——是脱敏的。这一点有直接的实操意义:CI 上出了问题要贴诊断结果给别人看,用这个输出,比截一屏日志安全。同理,codex mcp list 本机实测 Env 列里的环境变量值会被打成 *****,只显示键名,也可以放心贴。
另外有三个容易出错的位置,不分先后,都值得在预检里顺手扫一眼:① 顶层选项和 exec 独有选项的位置摆反了;② -c 的值没整体加引号,被 shell 吃掉引号后按字面字符串处理,静默走偏;③ MCP server 的启动超时用的还是 10 秒默认值,任务还没开始就失败。
七、什么情况不适用
- 需要人判断的活儿别放进来。 非交互模式下没有审批交互,
-a never的语义是”从不询问,执行失败直接回传给模型”。凡是你希望有人看一眼再放行的操作,本来就不该走这条路。 --dangerously-bypass-approvals-and-sandbox别当省事开关。 官方原文是 “EXTREMELY DANGEROUS. Intended solely for running in environments that are externally sandboxed”,--dangerously-bypass-hook-trust也写着 “DANGEROUS. Intended only for automation that already vets hook sources”。它们的适用前提是外部已经有沙箱、hook 来源已经过审,不是”CI 里反正是容器所以随便开”。你的 runner 有没有到那个程度,得自己评估。- 需要联网的任务要单独确认。 配置键
web_search默认值是cached,既不是关闭也不是实时;CLI 的--search对应开实时搜索,且官方说明写明启用后无逐次调用审批。另外sandbox_workspace_write.network_access控制 workspace-write 沙箱内是否允许出网——CI 里要装依赖的场景,这两处得对上。 - 别指望它对成本和耗时给承诺。 本文的所有实测结论都来自只读命令,我们没有发起过任何模型对话请求,因此没有任何耗时、token 消耗或模型表现数据可引用。要评估流水线成本,只能自己在小范围跑一段时间实测。
exec-server、cloud这些别混进来。 在 codex-cli 0.147.0 上,codex exec-server(Run the standalone exec-server service)和codex cloud的子命令说明里都带着[EXPERIMENTAL]标签。生产流水线里用带这个标签的东西,得做好它下个版本就变的准备。
最后一句实在话:把 codex exec 塞进 CI,收益最大的通常不是”让它自动改代码提 PR”,而是那些读一堆东西、产出一段结论的活儿——整理发布说明、汇总构建失败原因、把长日志压成一段人话。这类任务用 -s read-only 就够,产出用 -o 落一个文件,失败了大不了这一步跳过,不会污染仓库。链路先从这里跑通,再谈要不要给它写权限。
相关阅读
- 给 Codex CLI 装上 shell 补全:一份不靠猜的落地清单
- 用
codex exec把任务写成可重复执行的命令 - 把
codex exec --json接进自己的流水线:命令怎么写、产出怎么接、怎么验收 - Codex 的六个使用面:一张图看懂该用哪个
本文依据 Codex 官方文档(learn.chatgpt.com/docs/ 的《Non-interactive mode》《Configuration Reference》《Authentication》《Codex CLI》《Sandbox》页面)整理,核对日 2026-08-09;文中标注「本机实测」的部分基于 codex-cli 0.147.0 / Windows 11 环境下的只读命令输出。产品功能、模型与价格以官方最新说明为准。