Skip to main content

本页解决什么问题

codex exec 是 Codex CLI 的非交互入口:给它一次提示词,它在当前工作目录中完成一轮任务,输出结果后退出。它适合脚本、批处理、定时任务和 CI,但不应被当成“把一段字符串丢进去就万事大吉”的黑盒。脚本真正依赖的是一组可观察的契约:
  • 输入从哪里来,是命令行参数、标准输入中的上下文,还是标准输入中的完整提示词;
  • 人类进度、最终答复和机器事件分别写到哪里;
  • 如何保存最终消息,如何用 JSON Schema 约束其结构;
  • 成功、任务失败、参数错误、超时和被信号中断时,脚本如何区分;
  • 会话是否持久化,下一次能否 resume;
  • 运行时拥有什么文件、网络、命令和凭据权限。
本页只修改和说明 codex exec 脚本模式。动作名称、参数名和默认值可能随 CLI 版本变化;开始接入生产流水线前,先运行:
PowerShell 中使用同样的命令:
下文的示例使用占位符、临时文件和只读模式。请在自己的测试仓库中先跑通,再根据任务实际需要增加写权限。

一、先建立正确的运行模型

exec 与交互模式的边界

直接运行 codex 通常进入交互式终端界面,适合边观察、边追问、边审批。codex exec 则完成一次非交互运行后退出,适合无人值守场景。它不会替脚本提供可靠的人工确认环节,因此安全性不能依赖“模型会理解提示词里的不要做某事”,而要依赖沙箱、工作目录、凭据隔离和流水线权限。
exec 的提示词是任务意图,不是安全边界。下面两条命令的安全性不同,原因在选项而不在文字:
第一条是否能修改,取决于本机版本和配置中的运行策略;第二条明确给出工作区写权限,但仍不代表模型一定只会触碰一个文件。脚本应在运行后检查 git diff、文件白名单和测试结果。

运行前的四项检查

进入仓库后,再启动 exec:
运行前确认:
  1. 当前目录是预期仓库,而不是生产检出、父目录或共享工作区。
  2. 当前分支和未提交改动已记录;需要改文件时优先使用独立分支或临时 worktree。
  3. 输入日志、PR 描述和 issue 内容已经脱敏,并被当作不可信文本。
  4. API key、SSH 私钥、云凭据和 .env 没有放入提示词、日志或工作区。

二、标准输入:三种输入形态

标准输入(stdin)是脚本向进程传数据的通道。codex exec 常见有三种形态,关键区别在于“提示词由谁提供”。

形态 A:参数是指令,stdin 是额外上下文

当命令行上有提示词,同时 stdin 有管道数据时,把参数中的文字当作任务指令,把 stdin 内容作为上下文材料。构建日志、测试输出和审查输入通常采用这种形态。
这里的 2>&1 是上游 npm test 的 shell 重定向:将上游 stderr 并入上游 stdout,确保错误日志进入管道。它不会改变 Codex 自己的 stdout/stderr 分流。若日志很长,先限长并脱敏:
不要默认把完整的 CI 环境、用户请求或服务响应直接送入模型。输入中的秘密即使没有显示在最终答复中,也可能进入进程日志、供应商记录或缓存。

形态 B:codex exec -,stdin 是完整提示词

当整个提示词由文件或上游程序生成时,用单独的 - 表示从 stdin 读取完整提示词:
也可以从脚本组合完整提示词:
这种形态不要再把同一份指令重复放在位置参数中。提示词文件属于可执行输入,应该像代码一样审查、版本管理和限制写入来源。

形态 C:没有可用 stdin 时的直接参数

一次性任务可直接写在参数中:
Shell 会先处理引号、变量、命令替换和重定向。不要把不可信字符串未经转义拼接到 shell 命令中。数据较大或包含换行时,优先使用 stdin 文件,而不是复杂的嵌套引号。

stdin 的可操作注意事项

  • 管道要提供有限、可结束的输入;持续输出的 tail -f、日志订阅或交互式命令可能让 exec 一直等待。
  • stdin 为空时,codex exec - 得到的提示词为空或无效,具体报错以本机版本为准。
  • 不要让密码、令牌或二进制文件直接经过 stdin;先过滤并确认编码。
  • 上游命令失败不一定等于 Codex 失败。Bash 默认只报告管道最后一个命令的状态,必要时使用 set -o pipefail。
  • 在 PowerShell 中,管道对象可能被格式化为文本;要把原始文本送入 stdin,可使用 Get-Content -Raw 或明确的原生进程重定向。

三、stdout、stderr 与日志契约

三条内容边界

不带 --json 时,脚本通常应把最终答复视为 stdout,把过程信息和诊断信息视为 stderr。终端会把两者同时显示,所以肉眼看起来像一条流;重定向和管道时则是两条独立通道。
summary.md 供后续程序或人工阅读,run.log 供排查。不要在生产脚本中无条件写 2>&1,否则过程信息会污染需要解析的 stdout。
只保存最终答复并在终端查看它:
这里 tee 接收的是 stdout;stderr 仍由当前终端显示。若要记录两类内容,明确分开文件:

-o / --output-last-message

-o <path>(长选项为 --output-last-message <path>)用于把最终消息写到指定文件。它适合 CI 保存人类可读摘要,同时让 stdout 继续用于管道或事件流:
使用前创建目录并检查路径:
文件写入成功不代表任务成功。进程可能在输出最终消息后仍因外部错误退出,也可能根本没有产生最终消息;脚本必须同时检查退出码、文件存在性和内容是否为空。 在 --json 下,stdout 是 JSONL 事件流,-o 仍用于保存最终消息。不要把 stdout 当作 Markdown;需要人读的摘要读 -o 文件,需要机器处理的过程读 JSONL。

跨平台重定向

Bash:
PowerShell:
PowerShell 的 $? 表示 PowerShell 命令是否成功,不等同于原生进程退出码;判断 codex 时使用 $LASTEXITCODE。

四、JSONL:给程序消费的事件流

JSONL 的含义

--json 让 stdout 变成 JSON Lines(JSONL):每一行是一个独立 JSON 对象,而不是一个跨多行的 JSON 数组。脚本应逐行解析,允许出现未来版本增加的未知事件类型。
事件名称和字段以本机版本为准。常见事件可能包括线程开始、轮次开始或结束、具体 item 完成、错误和用量信息。例如,下面只是形状示意,不应把字段集合硬编码成永久协议:
JSONL 解决的是“机器如何观察过程”,不等于最终答复的业务数据模型,也不改变沙箱、网络或凭据权限。

逐行解析原则

Bash 中可以用 jq 检查每行是否为 JSON,并提取事件类型:
不要假定每个版本都存在 .message、.usage 或固定的 item 字段。处理未知事件时记录并继续,遇到明确 error 事件时标记失败,最终仍以进程退出码为主。 PowerShell 可逐行解析:

事件流与摘要同时保存

CI 通常既需要机器判断,又需要人读报告:
事件流中的错误检查只是补充。不能因为找到了 turn.completed 就忽略非零退出码,也不能因为摘要存在就认定修改和测试都正确。

五、output schema:约束最终消息

--output-schema <schema-file> 用 JSON Schema 约束最终消息的结构,适合把 Codex 的结论交给下游程序。Schema 是输出格式约束,不是权限策略;它不能阻止文件写入、网络访问、命令执行或提示词注入。 先写一个最小 Schema 文件:
运行时把 Schema 路径传给 --output-schema,并保存最终消息:
随后由独立校验器验证文件,而不是只相信模型声称“符合 Schema”:
Schema 设计建议:
  • 用 required 固定下游真正需要的字段;
  • 对枚举、整数和可空值明确约束;
  • 谨慎使用 additionalProperties: false,升级提示词时要同步测试;
  • 为“没有发现问题”定义空数组,而不是让模型自由输出“无”;
  • 不把事件流 Schema 和最终消息 Schema 混为一谈;
  • 升级 CLI、模型或提示词后,对有效和无效样例都做回归校验。
如果 Schema 文件本身有语法错误、路径不可读或当前 CLI 不支持某个选项,进程通常会在启动或请求阶段失败。保留 stderr,并先运行 codex exec --help,不要通过放宽权限绕过格式错误。

六、退出码:把结果交给脚本判断

退出码是脚本最重要的控制信号。通用规则是:0 表示进程认为本次运行成功;非 0 表示失败或未完成。非零不应被解释为某一个固定原因,因为认证、参数、网络、模型、权限、输入、被中断和外部超时都可能导致非零。 不要用最终答复里的“成功”“完成”文字代替退出码,也不要只检查摘要文件是否存在。

Bash 中可靠捕获退出码

管道场景要避免丢失 Codex 的状态:
PIPESTATUS 必须在管道结束后立即读取;复杂流水线建议拆成临时文件,让上游和 Codex 的失败更容易区分。

PowerShell 中可靠捕获退出码

如何诊断非零退出

按顺序保留并检查:
  1. 完整的 stderr,而不是只看终端最后一行。
  2. codex --version、完整命令和工作目录;密钥值不要记录。
  3. 是否为参数、Schema、模型、认证、网络、Git 检查或沙箱错误。
  4. 是否被外部超时工具、CI runner 或用户发送的信号终止。
  5. 工作区是否发生了改动;失败不代表没有副作用。
某些版本或宿主环境可能使用不同的非零码映射。除非本机 --help 或正式文档明确规定,否则不要在业务逻辑中写死“退出码 2 一定是认证失败”之类的判断。业务脚本可采用“0 成功、其他失败,再用 stderr 分类”的策略。

七、resume 与 ephemeral

用 resume 连接两阶段任务

需要先分析、人工审查后再修改时,可以恢复上一轮持久化会话:
也可以使用明确会话 ID:
--last 通常用于当前工作目录下最近的会话;跨目录、不同 CODEX_HOME、不同操作系统用户或并发任务时,不要猜测“最近”是哪一次。需要精确恢复时保存会话 ID,并以 codex exec resume --help 核验参数顺序。 恢复不是免检通行证。恢复后重新检查:
上一轮的权限、目录和外部状态可能已经不再适用。第二阶段即使沿用上下文,也要重新声明写入范围、测试要求和禁止的外部副作用。

--ephemeral 的含义

--ephemeral 用于一次性运行,不持久化会话记录。它适合不需要以后恢复的短任务,但会牺牲可追溯性,因此不适合需要人工交接的多阶段流程:
--ephemeral 不等于:
  • 不向模型服务发送输入;
  • 不写 stdout、stderr 或 -o 指定的文件;
  • 不会进入 CI 日志或 shell 历史;
  • 不会自动删除模型生成的工作区改动;
  • 不会绕过网络、代理、组织策略或审计记录。
如果任务需要 resume,不要使用 --ephemeral,并把会话 ID、工作区状态和产物路径交给下一阶段。会话记录中可能包含源代码、提示词和工具输出,应按敏感数据处理。

八、超时:为进程设置外部上限

CLI 版本不一定提供统一的“整次 exec 超时”选项;MCP 工具超时、网络超时和模型请求超时也不等于整个进程的超时。生产脚本应由外层 runner 设置硬上限,超时后杀死进程、保存日志并让任务失败。

Bash:使用 timeout

GNU coreutils 环境可这样写:
timeout 的具体退出码属于外层工具,不要把它误认成 Codex 自身的业务码。Windows 原生环境通常没有 GNU timeout,CI 镜像中是否安装要先检查。

PowerShell:启动进程并等待

需要硬超时时,用 System.Diagnostics.Process 捕获两个输出文件并等待:
实际 PowerShell 脚本应对参数转义、进程树终止和子进程残留做测试。优先使用 CI 平台原生 timeout 设置,并在 runner 上确认超时确实终止整个进程组。

超时后的处理

超时后不要立即重试写入任务。先检查是否已经修改文件、创建提交、发出网络请求或生成外部资源。重试要有次数上限和幂等设计;对于可能产生副作用的任务,先使用只读模式或隔离 worktree。

九、权限与安全边界

沙箱是主要边界

脚本化运行没有人工逐步审批,必须从启动命令明确权限: 新脚本不要依赖已弃用的宽泛快捷标志;用明确的 --sandbox 表达权限意图。--ask-for-approval never 解决的是“无人值守时不等待审批”,不是给文件系统和网络增加安全边界。

工作目录与 Git 检查

exec 通常要求在 Git 仓库中运行。非 Git 临时目录只有在确认可回滚、内容不敏感且确实需要时,才考虑:
跳过检查不是备份,也不提供权限隔离。写入任务应使用专用临时目录或 worktree,并在运行前后比较路径和 diff:

CI 中的密钥边界

不要把 key 写进提示词、YAML、脚本参数或仓库文件。不要把 OPENAI_API_KEY 设置为 job 级环境变量,因为 checkout 后的构建脚本、测试、依赖安装钩子或第三方 action 可能读取它。将秘密只传给调用 Codex 的那一步,并限制后续步骤:
审查来自外部 PR 的内容时,PR 描述、提交信息、生成文件和日志都可能包含提示词注入。不要把外部文本与写权限、网络权限和高价值凭据组合使用。将 Codex 放在 job 的最后步骤,并设置最小的 permissions。

不可信输出不能直接执行

JSONL 的 item 字段、最终 Markdown 和 Schema 字段都是数据,不是授权。不要把模型输出直接拼入 bash -c、PowerShell Invoke-Expression、SQL、部署命令或 GitHub Actions 表达式。若确实需要执行下游动作,使用固定的枚举映射和独立校验:

十、Bash CI 脚本模板

下面模板展示一次只读审查:输入、事件、摘要、stderr、退出码和产物都分开保存。
这个模板没有自动合并、自动提交或自动发布。人类或单独的质量门禁步骤应审查摘要、事件、diff 和测试结果。

十一、PowerShell CI 脚本模板

PowerShell 脚本若把上游命令接到 Codex,建议先将上游输出写入受控文件,再用 Get-Content -Raw 作为输入,以便分别记录上游退出码和 Codex 退出码。

十二、失败诊断清单

一直等待或超时

  • 检查 stdin 是否来自不会结束的管道。
  • 检查是否误用了交互式 codex 而不是 codex exec。
  • 检查网络、代理、认证和 runner 的进程超时。
  • 查看 stderr;不要只看 stdout 是否为空。
  • 终止后检查工作区和外部副作用,再决定是否重试。

结果文件为空或没有生成

  • 先看退出码,再看 stderr。
  • 确认父目录存在且当前用户可写。
  • 确认任务没有在认证、参数、Schema 或网络阶段提前失败。
  • 不要把 -o 文件当成成功标志。

JSONL 解析失败

  • 确认命令包含 --json,并且没有把 stderr 合并到 stdout。
  • 检查是否混入 shell 的自定义 printf 或第三方工具输出。
  • 按行解析,不要用“整个文件是一个 JSON 数组”的假设。
  • 对未知 type 保持兼容,升级 CLI 后重跑样例。

resume 找不到会话

  • 确认第一次运行没有使用 --ephemeral。
  • 确认账号、CODEX_HOME、操作系统用户和工作目录一致。
  • 保存并使用明确会话 ID,不要依赖跨目录的 --last。
  • 运行 codex exec resume --help,不要照抄旧版本参数。

明明要求修改却没有改动

  • 检查是否仍是 read-only 沙箱。
  • 检查工作目录、分支和文件权限。
  • 检查任务是否只要求输出建议。
  • 如果确实需要写入,使用隔离 worktree 和 workspace-write,再用 diff 和测试验收。

CI 中出现 401、权限拒绝或认证错误

  • 确认 Secret 名称和作用域,没有把 key 放在 job 级 env。
  • 确认 --ignore-user-config 没有隐藏认证所依赖的配置;受控 CI 应让认证来自 Secret 或环境变量。
  • 检查模型、代理、网络策略和 runner 平台。
  • 不要把真实 token 粘贴到日志、issue 或诊断报告。

十三、交付前验收

一次合格的 codex exec 脚本至少应满足:
  • codex exec --help 与目标环境版本已经核验;
  • stdin 的来源、结束条件和脱敏策略明确;
  • stdout、stderr、JSONL 和最终摘要分开保存;
  • 通过 $?、$LASTEXITCODE 或等价机制检查退出码;
  • -o 文件存在性和内容非空经过检查;
  • JSONL 按行解析,未知事件不会让脚本无故崩溃;
  • output schema 由独立校验器验证;
  • 超时有外部硬上限,并处理进程终止后的残留状态;
  • 运行在最小沙箱、最小目录和最小凭据范围内;
  • 失败后保留 stderr、事件流、摘要和工作区 diff;
  • 没有自动提交、推送、合并、发布或执行不可信输出。
常用的最终检查命令:
若任务允许写文件,再补充:

小结

把 codex exec 接入工程流程时,最重要的不是记住一条最长命令,而是建立明确的输入输出和失败契约: 最终原则是:提示词表达意图,沙箱和流水线策略定义边界;stdout 和 JSONL 供程序消费,stderr 供诊断;退出码决定流程是否成功,摘要和 diff 负责让人验收。参考资料:参考/codex/28-noninteractive.md、参考/codex/08-cli.md、参考/codex/27-automation.md。