本页解决什么问题
codex exec 是 Codex CLI 的非交互入口:给它一次提示词,它在当前工作目录中完成一轮任务,输出结果后退出。它适合脚本、批处理、定时任务和 CI,但不应被当成“把一段字符串丢进去就万事大吉”的黑盒。脚本真正依赖的是一组可观察的契约:
- 输入从哪里来,是命令行参数、标准输入中的上下文,还是标准输入中的完整提示词;
- 人类进度、最终答复和机器事件分别写到哪里;
- 如何保存最终消息,如何用 JSON Schema 约束其结构;
- 成功、任务失败、参数错误、超时和被信号中断时,脚本如何区分;
- 会话是否持久化,下一次能否
resume; - 运行时拥有什么文件、网络、命令和凭据权限。
codex exec 脚本模式。动作名称、参数名和默认值可能随 CLI 版本变化;开始接入生产流水线前,先运行:
一、先建立正确的运行模型
exec 与交互模式的边界
直接运行 codex 通常进入交互式终端界面,适合边观察、边追问、边审批。codex exec 则完成一次非交互运行后退出,适合无人值守场景。它不会替脚本提供可靠的人工确认环节,因此安全性不能依赖“模型会理解提示词里的不要做某事”,而要依赖沙箱、工作目录、凭据隔离和流水线权限。
exec 的提示词是任务意图,不是安全边界。下面两条命令的安全性不同,原因在选项而不在文字:
git diff、文件白名单和测试结果。
运行前的四项检查
进入仓库后,再启动exec:
- 当前目录是预期仓库,而不是生产检出、父目录或共享工作区。
- 当前分支和未提交改动已记录;需要改文件时优先使用独立分支或临时 worktree。
- 输入日志、PR 描述和 issue 内容已经脱敏,并被当作不可信文本。
- API key、SSH 私钥、云凭据和
.env没有放入提示词、日志或工作区。
二、标准输入:三种输入形态
标准输入(stdin)是脚本向进程传数据的通道。codex exec 常见有三种形态,关键区别在于“提示词由谁提供”。
形态 A:参数是指令,stdin 是额外上下文
当命令行上有提示词,同时 stdin 有管道数据时,把参数中的文字当作任务指令,把 stdin 内容作为上下文材料。构建日志、测试输出和审查输入通常采用这种形态。2>&1 是上游 npm test 的 shell 重定向:将上游 stderr 并入上游 stdout,确保错误日志进入管道。它不会改变 Codex 自己的 stdout/stderr 分流。若日志很长,先限长并脱敏:
形态 B:codex exec -,stdin 是完整提示词
当整个提示词由文件或上游程序生成时,用单独的 - 表示从 stdin 读取完整提示词:
形态 C:没有可用 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 命令是否成功,不等同于原生进程退出码;判断 codex 时使用 $LASTEXITCODE。
四、JSONL:给程序消费的事件流
JSONL 的含义
--json 让 stdout 变成 JSON Lines(JSONL):每一行是一个独立 JSON 对象,而不是一个跨多行的 JSON 数组。脚本应逐行解析,允许出现未来版本增加的未知事件类型。
逐行解析原则
Bash 中可以用jq 检查每行是否为 JSON,并提取事件类型:
.message、.usage 或固定的 item 字段。处理未知事件时记录并继续,遇到明确 error 事件时标记失败,最终仍以进程退出码为主。
PowerShell 可逐行解析:
事件流与摘要同时保存
CI 通常既需要机器判断,又需要人读报告:turn.completed 就忽略非零退出码,也不能因为摘要存在就认定修改和测试都正确。
五、output schema:约束最终消息
--output-schema <schema-file> 用 JSON Schema 约束最终消息的结构,适合把 Codex 的结论交给下游程序。Schema 是输出格式约束,不是权限策略;它不能阻止文件写入、网络访问、命令执行或提示词注入。
先写一个最小 Schema 文件:
--output-schema,并保存最终消息:
- 用
required固定下游真正需要的字段; - 对枚举、整数和可空值明确约束;
- 谨慎使用
additionalProperties: false,升级提示词时要同步测试; - 为“没有发现问题”定义空数组,而不是让模型自由输出“无”;
- 不把事件流 Schema 和最终消息 Schema 混为一谈;
- 升级 CLI、模型或提示词后,对有效和无效样例都做回归校验。
codex exec --help,不要通过放宽权限绕过格式错误。
六、退出码:把结果交给脚本判断
退出码是脚本最重要的控制信号。通用规则是:0 表示进程认为本次运行成功;非 0 表示失败或未完成。非零不应被解释为某一个固定原因,因为认证、参数、网络、模型、权限、输入、被中断和外部超时都可能导致非零。
不要用最终答复里的“成功”“完成”文字代替退出码,也不要只检查摘要文件是否存在。
Bash 中可靠捕获退出码
PIPESTATUS 必须在管道结束后立即读取;复杂流水线建议拆成临时文件,让上游和 Codex 的失败更容易区分。
PowerShell 中可靠捕获退出码
如何诊断非零退出
按顺序保留并检查:- 完整的 stderr,而不是只看终端最后一行。
codex --version、完整命令和工作目录;密钥值不要记录。- 是否为参数、Schema、模型、认证、网络、Git 检查或沙箱错误。
- 是否被外部超时工具、CI runner 或用户发送的信号终止。
- 工作区是否发生了改动;失败不代表没有副作用。
--help 或正式文档明确规定,否则不要在业务逻辑中写死“退出码 2 一定是认证失败”之类的判断。业务脚本可采用“0 成功、其他失败,再用 stderr 分类”的策略。
七、resume 与 ephemeral
用 resume 连接两阶段任务
需要先分析、人工审查后再修改时,可以恢复上一轮持久化会话:
--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 捕获两个输出文件并等待:
超时后的处理
超时后不要立即重试写入任务。先检查是否已经修改文件、创建提交、发出网络请求或生成外部资源。重试要有次数上限和幂等设计;对于可能产生副作用的任务,先使用只读模式或隔离 worktree。九、权限与安全边界
沙箱是主要边界
脚本化运行没有人工逐步审批,必须从启动命令明确权限:
新脚本不要依赖已弃用的宽泛快捷标志;用明确的
--sandbox 表达权限意图。--ask-for-approval never 解决的是“无人值守时不等待审批”,不是给文件系统和网络增加安全边界。
工作目录与 Git 检查
exec 通常要求在 Git 仓库中运行。非 Git 临时目录只有在确认可回滚、内容不敏感且确实需要时,才考虑:
CI 中的密钥边界
不要把 key 写进提示词、YAML、脚本参数或仓库文件。不要把OPENAI_API_KEY 设置为 job 级环境变量,因为 checkout 后的构建脚本、测试、依赖安装钩子或第三方 action 可能读取它。将秘密只传给调用 Codex 的那一步,并限制后续步骤:
permissions。
不可信输出不能直接执行
JSONL 的item 字段、最终 Markdown 和 Schema 字段都是数据,不是授权。不要把模型输出直接拼入 bash -c、PowerShell Invoke-Expression、SQL、部署命令或 GitHub Actions 表达式。若确实需要执行下游动作,使用固定的枚举映射和独立校验:
十、Bash CI 脚本模板
下面模板展示一次只读审查:输入、事件、摘要、stderr、退出码和产物都分开保存。十一、PowerShell CI 脚本模板
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。