> ## Documentation Index
> Fetch the complete documentation index at: https://aicoding.cscitech.top/llms.txt
> Use this file to discover all available pages before exploring further.

# 03-codex exec 脚本模式

> 掌握 codex exec 的标准输入、输出流、JSONL、结构化结果、会话恢复、退出码、超时与权限边界，并将它可靠接入 Bash、PowerShell 和 CI。

## 本页解决什么问题

`codex exec` 是 Codex CLI 的非交互入口：给它一次提示词，它在当前工作目录中完成一轮任务，输出结果后退出。它适合脚本、批处理、定时任务和 CI，但不应被当成“把一段字符串丢进去就万事大吉”的黑盒。脚本真正依赖的是一组可观察的契约：

* 输入从哪里来，是命令行参数、标准输入中的上下文，还是标准输入中的完整提示词；
* 人类进度、最终答复和机器事件分别写到哪里；
* 如何保存最终消息，如何用 JSON Schema 约束其结构；
* 成功、任务失败、参数错误、超时和被信号中断时，脚本如何区分；
* 会话是否持久化，下一次能否 `resume`；
* 运行时拥有什么文件、网络、命令和凭据权限。

本页只修改和说明 `codex exec` 脚本模式。动作名称、参数名和默认值可能随 CLI 版本变化；开始接入生产流水线前，先运行：

```bash theme={null}
codex --version
codex exec --help
codex exec resume --help
```

PowerShell 中使用同样的命令：

```powershell theme={null}
codex --version
codex exec --help
codex exec resume --help
```

下文的示例使用占位符、临时文件和只读模式。请在自己的测试仓库中先跑通，再根据任务实际需要增加写权限。

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

### `exec` 与交互模式的边界

直接运行 `codex` 通常进入交互式终端界面，适合边观察、边追问、边审批。`codex exec` 则完成一次非交互运行后退出，适合无人值守场景。它不会替脚本提供可靠的人工确认环节，因此安全性不能依赖“模型会理解提示词里的不要做某事”，而要依赖沙箱、工作目录、凭据隔离和流水线权限。

```bash theme={null}
# 人工在场的探索或调试
codex

# 一次性、非交互的只读分析
codex exec --sandbox read-only "审查当前改动，列出高风险问题和缺失测试"
```

`exec` 的提示词是任务意图，不是安全边界。下面两条命令的安全性不同，原因在选项而不在文字：

```bash theme={null}
codex exec "只修改 src/parser.ts"
codex exec --sandbox workspace-write "只修改 src/parser.ts"
```

第一条是否能修改，取决于本机版本和配置中的运行策略；第二条明确给出工作区写权限，但仍不代表模型一定只会触碰一个文件。脚本应在运行后检查 `git diff`、文件白名单和测试结果。

### 运行前的四项检查

进入仓库后，再启动 `exec`：

```bash theme={null}
pwd
printf 'branch: '; git branch --show-current
git status --short
codex exec --help
```

运行前确认：

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

## 二、标准输入：三种输入形态

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

### 形态 A：参数是指令，stdin 是额外上下文

当命令行上有提示词，同时 stdin 有管道数据时，把参数中的文字当作任务指令，把 stdin 内容作为上下文材料。构建日志、测试输出和审查输入通常采用这种形态。

```bash theme={null}
npm test 2>&1 \
  | codex exec --sandbox read-only \
      "分析下面的测试输出，归纳根因并给出最小修复方案；不要修改文件"
```

这里的 `2>&1` 是上游 `npm test` 的 shell 重定向：将上游 stderr 并入上游 stdout，确保错误日志进入管道。它不会改变 Codex 自己的 stdout/stderr 分流。若日志很长，先限长并脱敏：

```bash theme={null}
npm test 2>&1 \
  | tail -n 300 \
  | sed -E 's/(token|password|api[_-]?key)=([^ ]+)/\1=<REDACTED>/Ig' \
  | codex exec --sandbox read-only "只分析这段测试日志"
```

不要默认把完整的 CI 环境、用户请求或服务响应直接送入模型。输入中的秘密即使没有显示在最终答复中，也可能进入进程日志、供应商记录或缓存。

### 形态 B：`codex exec -`，stdin 是完整提示词

当整个提示词由文件或上游程序生成时，用单独的 `-` 表示从 stdin 读取完整提示词：

```bash theme={null}
cat prompts/review.txt | codex exec - --sandbox read-only
```

也可以从脚本组合完整提示词：

```bash theme={null}
{
  printf '%s\n\n' '审查当前仓库。输出问题、证据、严重度和建议。'
  printf '%s\n' '附加约束：不要修改文件，不要执行网络操作。'
} | codex exec - --sandbox read-only
```

这种形态不要再把同一份指令重复放在位置参数中。提示词文件属于可执行输入，应该像代码一样审查、版本管理和限制写入来源。

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

一次性任务可直接写在参数中：

```bash theme={null}
codex exec --sandbox read-only "总结当前仓库的构建、测试和发布入口"
```

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。终端会把两者同时显示，所以肉眼看起来像一条流；重定向和管道时则是两条独立通道。

```bash theme={null}
# stdout：最终答复；stderr：过程与诊断
codex exec --sandbox read-only "总结当前改动" \
  > summary.md \
  2> run.log
```

`summary.md` 供后续程序或人工阅读，`run.log` 供排查。不要在生产脚本中无条件写 `2>&1`，否则过程信息会污染需要解析的 stdout。

```bash theme={null}
# 查看结果，同时保留单独的过程日志
codex exec --sandbox read-only "总结当前改动" \
  > summary.md \
  2> run.log
cat summary.md
cat run.log >&2
```

只保存最终答复并在终端查看它：

```bash theme={null}
codex exec --sandbox read-only "生成发布说明" | tee release-notes.md
```

这里 `tee` 接收的是 stdout；stderr 仍由当前终端显示。若要记录两类内容，明确分开文件：

```bash theme={null}
codex exec --sandbox read-only "检查代码" \
  > result.txt \
  2> progress.log
```

### `-o` / `--output-last-message`

`-o <path>`（长选项为 `--output-last-message <path>`）用于把最终消息写到指定文件。它适合 CI 保存人类可读摘要，同时让 stdout 继续用于管道或事件流：

```bash theme={null}
codex exec --sandbox read-only \
  -o artifacts/codex-summary.md \
  "审查当前改动并总结结论"
```

使用前创建目录并检查路径：

```bash theme={null}
mkdir -p artifacts
codex exec --sandbox read-only \
  --output-last-message artifacts/codex-summary.md \
  "生成审查摘要"
```

文件写入成功不代表任务成功。进程可能在输出最终消息后仍因外部错误退出，也可能根本没有产生最终消息；脚本必须同时检查退出码、文件存在性和内容是否为空。

在 `--json` 下，stdout 是 JSONL 事件流，`-o` 仍用于保存最终消息。不要把 stdout 当作 Markdown；需要人读的摘要读 `-o` 文件，需要机器处理的过程读 JSONL。

### 跨平台重定向

Bash：

```bash theme={null}
set -o pipefail
mkdir -p artifacts
codex exec --sandbox read-only \
  --json \
  --output-last-message artifacts/summary.md \
  "审查当前改动" \
  > artifacts/events.jsonl \
  2> artifacts/stderr.log
status=$?
printf 'codex exit code: %s\n' "$status" >&2
exit "$status"
```

PowerShell：

```powershell theme={null}
New-Item -ItemType Directory -Force artifacts | Out-Null
codex exec --sandbox read-only `
  --json `
  --output-last-message artifacts/summary.md `
  "审查当前改动" `
  1> artifacts/events.jsonl `
  2> artifacts/stderr.log
$status = $LASTEXITCODE
"codex exit code: $status" | Write-Error
exit $status
```

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

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

### JSONL 的含义

`--json` 让 stdout 变成 JSON Lines（JSONL）：每一行是一个独立 JSON 对象，而不是一个跨多行的 JSON 数组。脚本应逐行解析，允许出现未来版本增加的未知事件类型。

```bash theme={null}
codex exec --json --sandbox read-only "检查当前改动" > events.jsonl
```

事件名称和字段以本机版本为准。常见事件可能包括线程开始、轮次开始或结束、具体 item 完成、错误和用量信息。例如，下面只是形状示意，不应把字段集合硬编码成永久协议：

```jsonl theme={null}
{"type":"thread.started","thread_id":"..."}
{"type":"turn.started"}
{"type":"item.completed","item":{"type":"agent_message","text":"..."}}
{"type":"turn.completed","usage":{"input_tokens":123,"output_tokens":45}}
```

JSONL 解决的是“机器如何观察过程”，不等于最终答复的业务数据模型，也不改变沙箱、网络或凭据权限。

### 逐行解析原则

Bash 中可以用 `jq` 检查每行是否为 JSON，并提取事件类型：

```bash theme={null}
if ! jq -e . < events.jsonl > /dev/null; then
  printf '%s\n' 'JSONL 中存在无法解析的行' >&2
  exit 1
fi
jq -r 'select(.type == "error") | .message // .error // "unknown error"' \
  events.jsonl
```

不要假定每个版本都存在 `.message`、`.usage` 或固定的 `item` 字段。处理未知事件时记录并继续，遇到明确 `error` 事件时标记失败，最终仍以进程退出码为主。

PowerShell 可逐行解析：

```powershell theme={null}
$errors = @()
Get-Content artifacts/events.jsonl | ForEach-Object {
  if ([string]::IsNullOrWhiteSpace($_)) { return }
  try {
    $event = $_ | ConvertFrom-Json -ErrorAction Stop
    if ($event.type -eq 'error') { $errors += $event }
  } catch {
    throw "无法解析 JSONL：$($_)"
  }
}
if ($errors.Count -gt 0) {
  $errors | ConvertTo-Json -Depth 10
  exit 1
}
```

### 事件流与摘要同时保存

CI 通常既需要机器判断，又需要人读报告：

```bash theme={null}
set -o pipefail
mkdir -p artifacts
codex exec \
  --sandbox read-only \
  --json \
  --output-last-message artifacts/codex-summary.md \
  "审查本次提交，给出可验证的风险结论" \
  > artifacts/codex-events.jsonl \
  2> artifacts/codex-stderr.log
status=$?

if [ "$status" -ne 0 ]; then
  printf 'Codex failed with exit code %s\n' "$status" >&2
  exit "$status"
fi

test -s artifacts/codex-summary.md
jq -e 'select(.type == "error")' artifacts/codex-events.jsonl >/dev/null \
  && { printf '%s\n' '事件流报告 error' >&2; exit 1; } \
  || true
```

事件流中的错误检查只是补充。不能因为找到了 `turn.completed` 就忽略非零退出码，也不能因为摘要存在就认定修改和测试都正确。

## 五、output schema：约束最终消息

`--output-schema <schema-file>` 用 JSON Schema 约束最终消息的结构，适合把 Codex 的结论交给下游程序。Schema 是输出格式约束，不是权限策略；它不能阻止文件写入、网络访问、命令执行或提示词注入。

先写一个最小 Schema 文件：

```json theme={null}
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "summary": { "type": "string" },
    "findings": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "severity": { "type": "string", "enum": ["high", "medium", "low"] },
          "file": { "type": "string" },
          "line": { "type": ["integer", "null"], "minimum": 1 },
          "detail": { "type": "string" }
        },
        "required": ["severity", "file", "line", "detail"]
      }
    }
  },
  "required": ["summary", "findings"]
}
```

运行时把 Schema 路径传给 `--output-schema`，并保存最终消息：

```bash theme={null}
codex exec --sandbox read-only \
  --output-schema schema/review.json \
  --output-last-message artifacts/review.json \
  "按给定 Schema 输出当前改动的审查结果；只报告有证据的问题"
```

随后由独立校验器验证文件，而不是只相信模型声称“符合 Schema”：

```bash theme={null}
jq -e . artifacts/review.json >/dev/null
# 具体 JSON Schema 校验器按项目工具链选择，例如 ajv、check-jsonschema 或 Python jsonschema。
```

Schema 设计建议：

* 用 `required` 固定下游真正需要的字段；
* 对枚举、整数和可空值明确约束；
* 谨慎使用 `additionalProperties: false`，升级提示词时要同步测试；
* 为“没有发现问题”定义空数组，而不是让模型自由输出“无”；
* 不把事件流 Schema 和最终消息 Schema 混为一谈；
* 升级 CLI、模型或提示词后，对有效和无效样例都做回归校验。

如果 Schema 文件本身有语法错误、路径不可读或当前 CLI 不支持某个选项，进程通常会在启动或请求阶段失败。保留 stderr，并先运行 `codex exec --help`，不要通过放宽权限绕过格式错误。

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

退出码是脚本最重要的控制信号。通用规则是：`0` 表示进程认为本次运行成功；非 `0` 表示失败或未完成。非零不应被解释为某一个固定原因，因为认证、参数、网络、模型、权限、输入、被中断和外部超时都可能导致非零。

不要用最终答复里的“成功”“完成”文字代替退出码，也不要只检查摘要文件是否存在。

### Bash 中可靠捕获退出码

```bash theme={null}
#!/usr/bin/env bash
set -u
set -o pipefail

mkdir -p artifacts
set +e
codex exec --sandbox read-only \
  --output-last-message artifacts/summary.md \
  "审查当前改动并输出结论" \
  > artifacts/result.txt \
  2> artifacts/stderr.log
status=$?
set -e

if [ "$status" -eq 0 ]; then
  test -s artifacts/summary.md || {
    printf '%s\n' '进程成功但最终摘要为空' >&2
    exit 1
  }
else
  printf 'codex exec failed: exit code %s\n' "$status" >&2
  sed -n '1,120p' artifacts/stderr.log >&2
  exit "$status"
fi
```

管道场景要避免丢失 Codex 的状态：

```bash theme={null}
set -o pipefail
npm test 2>&1 | codex exec --sandbox read-only "分析测试输出"
pipeline_status=("${PIPESTATUS[@]}")
upstream=${pipeline_status[0]}
status=${pipeline_status[1]}
printf 'upstream=%s codex=%s\n' "$upstream" "$status" >&2
```

`PIPESTATUS` 必须在管道结束后立即读取；复杂流水线建议拆成临时文件，让上游和 Codex 的失败更容易区分。

### PowerShell 中可靠捕获退出码

```powershell theme={null}
$ErrorActionPreference = 'Stop'
New-Item -ItemType Directory -Force artifacts | Out-Null

& codex exec --sandbox read-only `
  --output-last-message artifacts/summary.md `
  "审查当前改动并输出结论" `
  1> artifacts/result.txt `
  2> artifacts/stderr.log
$status = $LASTEXITCODE

if ($status -ne 0) {
  Write-Error "codex exec 失败，退出码：$status"
  Get-Content artifacts/stderr.log -TotalCount 120 | Write-Error
  exit $status
}

if (-not (Test-Path artifacts/summary.md) -or
    (Get-Item artifacts/summary.md).Length -eq 0) {
  Write-Error '进程成功但最终摘要为空'
  exit 1
}
```

### 如何诊断非零退出

按顺序保留并检查：

1. 完整的 stderr，而不是只看终端最后一行。
2. `codex --version`、完整命令和工作目录；密钥值不要记录。
3. 是否为参数、Schema、模型、认证、网络、Git 检查或沙箱错误。
4. 是否被外部超时工具、CI runner 或用户发送的信号终止。
5. 工作区是否发生了改动；失败不代表没有副作用。

某些版本或宿主环境可能使用不同的非零码映射。除非本机 `--help` 或正式文档明确规定，否则不要在业务逻辑中写死“退出码 2 一定是认证失败”之类的判断。业务脚本可采用“`0` 成功、其他失败，再用 stderr 分类”的策略。

## 七、resume 与 ephemeral

### 用 `resume` 连接两阶段任务

需要先分析、人工审查后再修改时，可以恢复上一轮持久化会话：

```bash theme={null}
codex exec --sandbox read-only \
  "审查当前改动，列出问题但不要修改文件"

codex exec resume --last --sandbox workspace-write \
  "根据上一轮发现的问题实施最小修复，然后运行相关测试"
```

也可以使用明确会话 ID：

```bash theme={null}
codex exec resume <SESSION_ID> --sandbox workspace-write \
  "继续上次任务，先检查当前 diff"
```

`--last` 通常用于当前工作目录下最近的会话；跨目录、不同 `CODEX_HOME`、不同操作系统用户或并发任务时，不要猜测“最近”是哪一次。需要精确恢复时保存会话 ID，并以 `codex exec resume --help` 核验参数顺序。

恢复不是免检通行证。恢复后重新检查：

```bash theme={null}
pwd
git branch --show-current
git status --short
git diff --stat
```

上一轮的权限、目录和外部状态可能已经不再适用。第二阶段即使沿用上下文，也要重新声明写入范围、测试要求和禁止的外部副作用。

### `--ephemeral` 的含义

`--ephemeral` 用于一次性运行，不持久化会话记录。它适合不需要以后恢复的短任务，但会牺牲可追溯性，因此不适合需要人工交接的多阶段流程：

```bash theme={null}
codex exec --ephemeral --sandbox read-only \
  "一次性总结当前仓库，不需要恢复会话"
```

`--ephemeral` 不等于：

* 不向模型服务发送输入；
* 不写 stdout、stderr 或 `-o` 指定的文件；
* 不会进入 CI 日志或 shell 历史；
* 不会自动删除模型生成的工作区改动；
* 不会绕过网络、代理、组织策略或审计记录。

如果任务需要 `resume`，不要使用 `--ephemeral`，并把会话 ID、工作区状态和产物路径交给下一阶段。会话记录中可能包含源代码、提示词和工具输出，应按敏感数据处理。

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

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

### Bash：使用 `timeout`

GNU coreutils 环境可这样写：

```bash theme={null}
set -o pipefail
mkdir -p artifacts

set +e
timeout --signal=TERM --kill-after=20s 10m \
  codex exec --sandbox read-only \
    --json \
    --output-last-message artifacts/summary.md \
    "审查当前改动" \
    > artifacts/events.jsonl \
    2> artifacts/stderr.log
status=$?
set -e

case "$status" in
  0) printf '%s\n' 'Codex completed' ;;
  124|137) printf '%s\n' 'Codex timed out or was killed' >&2; exit 124 ;;
  *) printf 'Codex failed: %s\n' "$status" >&2; exit "$status" ;;
esac
```

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

### PowerShell：启动进程并等待

需要硬超时时，用 `System.Diagnostics.Process` 捕获两个输出文件并等待：

```powershell theme={null}
$psi = [Diagnostics.ProcessStartInfo]::new()
$psi.FileName = 'codex'
$psi.Arguments = 'exec --sandbox read-only "审查当前改动"'
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.UseShellExecute = $false
$psi.CreateNoWindow = $true

$p = [Diagnostics.Process]::new()
$p.StartInfo = $psi
[void]$p.Start()

if (-not $p.WaitForExit(600000)) {
  $p.Kill($true)
  throw 'codex exec 超时，进程已终止'
}

$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
[IO.File]::WriteAllText('artifacts/result.txt', $stdout)
[IO.File]::WriteAllText('artifacts/stderr.log', $stderr)
if ($p.ExitCode -ne 0) { exit $p.ExitCode }
```

实际 PowerShell 脚本应对参数转义、进程树终止和子进程残留做测试。优先使用 CI 平台原生 timeout 设置，并在 runner 上确认超时确实终止整个进程组。

### 超时后的处理

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

## 九、权限与安全边界

### 沙箱是主要边界

脚本化运行没有人工逐步审批，必须从启动命令明确权限：

| 模式                   | 适用场景                    | 需要注意                 |
| -------------------- | ----------------------- | -------------------- |
| `read-only`          | 审查、总结、日志分析、生成建议         | 只读仍可能暴露送入模型的敏感内容     |
| `workspace-write`    | 在隔离仓库中修改代码并运行项目检查       | 需要检查 diff、脚本副作用和生成文件 |
| `danger-full-access` | 仅限强隔离容器或专用 runner 的受控任务 | 不应在日常开发机或多租户环境使用     |

新脚本不要依赖已弃用的宽泛快捷标志；用明确的 `--sandbox` 表达权限意图。`--ask-for-approval never` 解决的是“无人值守时不等待审批”，不是给文件系统和网络增加安全边界。

### 工作目录与 Git 检查

`exec` 通常要求在 Git 仓库中运行。非 Git 临时目录只有在确认可回滚、内容不敏感且确实需要时，才考虑：

```bash theme={null}
codex exec --skip-git-repo-check --sandbox read-only \
  "分析这个临时目录中的样例文件"
```

跳过检查不是备份，也不提供权限隔离。写入任务应使用专用临时目录或 worktree，并在运行前后比较路径和 diff：

```bash theme={null}
before=$(git status --short)
codex exec --sandbox workspace-write "修复指定测试失败，不要提交或推送"
status=$?
after=$(git status --short)
printf '%s\n' "$before" >&2
printf '%s\n' "$after" >&2
exit "$status"
```

### CI 中的密钥边界

不要把 key 写进提示词、YAML、脚本参数或仓库文件。不要把 `OPENAI_API_KEY` 设置为 job 级环境变量，因为 checkout 后的构建脚本、测试、依赖安装钩子或第三方 action 可能读取它。将秘密只传给调用 Codex 的那一步，并限制后续步骤：

```yaml theme={null}
- name: Run Codex
  uses: openai/codex-action@v1
  with:
    openai-api-key: ${{ secrets.OPENAI_API_KEY }}
    sandbox: read-only
    prompt-file: .github/codex/prompts/review.md
    output-file: codex-summary.md
```

审查来自外部 PR 的内容时，PR 描述、提交信息、生成文件和日志都可能包含提示词注入。不要把外部文本与写权限、网络权限和高价值凭据组合使用。将 Codex 放在 job 的最后步骤，并设置最小的 `permissions`。

### 不可信输出不能直接执行

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

```bash theme={null}
# 只接受固定值，不执行模型返回的任意命令
case "${DECISION:-}" in
  approve) ./scripts/approve.sh ;;
  reject) ./scripts/reject.sh ;;
  *) printf '%s\n' 'invalid decision' >&2; exit 1 ;;
esac
```

## 十、Bash CI 脚本模板

下面模板展示一次只读审查：输入、事件、摘要、stderr、退出码和产物都分开保存。

```bash theme={null}
#!/usr/bin/env bash
set -Eeuo pipefail

ROOT="$(git rev-parse --show-toplevel)"
cd "$ROOT"
mkdir -p artifacts

codex --version > artifacts/codex-version.txt

git diff --binary > artifacts/input.patch
if [ ! -s artifacts/input.patch ]; then
  printf '%s\n' '没有当前改动，停止审查' >&2
  exit 0
fi

set +e
timeout --signal=TERM --kill-after=20s 15m \
  codex exec \
    --sandbox read-only \
    --json \
    --output-last-message artifacts/summary.md \
    "审查当前工作区改动。只报告有文件和行号证据的问题；不要修改文件、提交或推送。" \
    > artifacts/events.jsonl \
    2> artifacts/stderr.log
codex_status=$?
set -e

if [ "$codex_status" -ne 0 ]; then
  printf 'codex exec failed: %s\n' "$codex_status" >&2
  exit "$codex_status"
fi

test -s artifacts/summary.md
while IFS= read -r line; do
  jq -e . >/dev/null <<<"$line" || {
    printf '%s\n' 'invalid JSONL event' >&2
    exit 1
  }
done < artifacts/events.jsonl

printf '%s\n' 'Codex review completed'
```

这个模板没有自动合并、自动提交或自动发布。人类或单独的质量门禁步骤应审查摘要、事件、diff 和测试结果。

## 十一、PowerShell CI 脚本模板

```powershell theme={null}
$ErrorActionPreference = 'Stop'
$root = (git rev-parse --show-toplevel).Trim()
Set-Location $root
New-Item -ItemType Directory -Force artifacts | Out-Null

codex --version | Set-Content artifacts/codex-version.txt
$patch = git diff --binary
if ([string]::IsNullOrWhiteSpace($patch)) {
  Write-Host '没有当前改动，停止审查'
  exit 0
}
$patch | Set-Content artifacts/input.patch

& codex exec --sandbox read-only `
  --json `
  --output-last-message artifacts/summary.md `
  "审查当前工作区改动。只报告有文件和行号证据的问题；不要修改文件、提交或推送。" `
  1> artifacts/events.jsonl `
  2> artifacts/stderr.log
$status = $LASTEXITCODE

if ($status -ne 0) {
  Write-Error "codex exec 失败：$status"
  Get-Content artifacts/stderr.log -TotalCount 160 | Write-Error
  exit $status
}

if (-not (Test-Path artifacts/summary.md) -or
    (Get-Item artifacts/summary.md).Length -eq 0) {
  Write-Error '最终摘要为空'
  exit 1
}

Get-Content artifacts/events.jsonl | ForEach-Object {
  if (-not [string]::IsNullOrWhiteSpace($_)) {
    $_ | ConvertFrom-Json -ErrorAction Stop | Out-Null
  }
}
Write-Host 'Codex review completed'
```

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；
* 没有自动提交、推送、合并、发布或执行不可信输出。

常用的最终检查命令：

```bash theme={null}
git status --short
git diff --check
git diff --stat
```

若任务允许写文件，再补充：

```bash theme={null}
git diff --name-only
git diff -- . ':!artifacts'
./scripts/test.sh
```

## 小结

把 `codex exec` 接入工程流程时，最重要的不是记住一条最长命令，而是建立明确的输入输出和失败契约：

| 目标              | 做法                                        |
| --------------- | ----------------------------------------- |
| 提供上下文           | 提示词参数 + stdin 管道                          |
| 让 stdin 成为完整提示词 | `codex exec -`                            |
| 保存最终答复          | `-o` / `--output-last-message`            |
| 让程序观察过程         | `--json`，逐行解析 JSONL                       |
| 约束最终数据          | `--output-schema`，再由独立校验器验证               |
| 判断成功            | 检查退出码，不依赖自然语言                             |
| 恢复两阶段任务         | 持久化会话后使用 `codex exec resume`              |
| 一次性不留会话         | `--ephemeral`，但不能依赖 `resume`              |
| 限制运行时间          | 用 Bash、PowerShell 或 CI runner 设置外部超时      |
| 控制风险            | `read-only` 起步，必要时才用隔离的 `workspace-write` |

最终原则是：提示词表达意图，沙箱和流水线策略定义边界；stdout 和 JSONL 供程序消费，stderr 供诊断；退出码决定流程是否成功，摘要和 diff 负责让人验收。参考资料：`参考/codex/28-noninteractive.md`、`参考/codex/08-cli.md`、`参考/codex/27-automation.md`。
