# 28 · 非交互模式 codex exec：把它塞进脚本和 CI 里跑

> 📚 **系列导航**：上一篇〔[27 自动化与 CI/CD](27-automation.md) 〕讲了「让 Codex 在流水线里自动干活」这件事的全貌——什么活该自动化、CI 里怎么摆。这一篇把镜头推到那条最核心的命令上：`codex exec`，也就是不开图形界面、一句话丢进去跑完就退的「非交互模式」。下一篇〔[29 Slack / Linear 与 SDK 集成](29-integrations.md) 〕再把它接到 IM 和工单系统里去。

兄弟们，今天聊一条你早晚绕不开的命令——`codex exec`。

前面第 08 篇「命令行 CLI」我提过它一嘴，说它像「点外卖」：下单（一句提示词）、厨房关门做完直接送到（结果打到终端），全程不用你在场。但那篇只是让你「知道有这么个东西」。**这一篇是把厨房后门打开，让你看清外卖是怎么做、怎么打包、怎么塞进自动化管道的。**

为啥要专门拎出来讲？因为交互模式（你敲 `codex` 进那个全屏界面）有个死穴：**它假设有个活人坐在屏幕前**——盯着对话、按回车批准、看 diff。可一旦你想「每天早上自动审一遍昨天的提交」「CI 跑挂了让它自动提个修复 PR」「把构建报错管道喂给它、让它吐根因总结」——**这些场景里压根没有活人**。这时候交互界面不光没用，还会卡在「等你批准」那一步死等。`codex exec` 就是为「没活人」这件事生的。

说白了，**学会 `codex exec`，是你从「用 Codex 这个工具」迈向「把 Codex 当成自己工作流里一个零件」的分水岭。**

**看完这一篇，你会拿到：**

- 一句话讲清 `codex exec` 和交互模式的本质区别，以及它专治哪几类「没活人」的场景
- 看懂它最关键的设计：**进度走 stderr、最终结果走 stdout**——这一刀切开，管道才好接
- `--json`（机器可读的过程事件）和 `-o` / `--output-last-message`（捞最终结果到文件）怎么搭配，CI 里两边都不耽误
- 非交互模式的沙箱默认是只读、要改文件得显式放开——附一张安全档位对照表
- stdin 管道的两种姿势（提示词 + 管道当上下文 / `codex exec -` 让 stdin 当整个提示词）分别啥时候用
- 一套能照抄的最小动手流程，把「跑一次 → 拿结果 → 写进脚本批量跑」亲手走通

> ⚠️ 下文凡涉及具体子命令、选项、默认行为，都以 Codex [官方文档](https://developers.openai.com/codex/noninteractive) 为准；模型名、版本这类随版本变的东西，看到时以你本地实际为准，本篇不写死。

---

## 01 先想清楚：非交互模式到底为谁而生

先给结论：**`codex exec` 解决的是「没有活人盯着」的场景**——脚本、定时任务、CI 流水线里，没人能实时批准、没人盯着对话框，活儿得自己跑完、结果得能被下一个程序接住。

回想前面那么多篇，咱们基本都在「交互模式」里折腾——敲 `codex` 进全屏界面，给一句指令，看它一步步干，它要动手了你点个同意。这模式九成的活儿都够用，**前提是你人在场**。

但有三类活儿，你根本不想在场、或者没法在场。

**第一类，重复到让人烦的活。** 比如「每天给昨天的 10 个提交生成一份发布说明」。这活儿没难度，就是烦——你不想每天早上手动开 Codex、复制提交、等它写、再粘出来。

**第二类，没有图形界面的环境。** CI 流水线、云服务器、Docker 容器里压根没有终端让你「盯着」，交互式那个全屏 TUI（终端用户界面，Terminal User Interface）根本起不来。

**第三类，要把 Codex 的输出喂给下一个程序。** 比如让它把一段日志总结成 Markdown 表格，结果直接写进文件、或者管道接给 `gh pr comment` 贴到 PR 评论区。这要求 Codex 的输出**干净得像个标准命令行工具**，而不是一屏花花绿绿的交互界面。

**类比：自动售货机 vs 有店员的柜台。** 交互模式像有店员的柜台——你说要啥、店员现做、中途能问你「加不加糖」、做好递给你。`codex exec` 像自动售货机——你按下按钮（一条命令），机器关起门自己运转，东西「咣当」掉进取货口（结果打到 stdout），**全程没有店员、不会有人问你问题、也不需要你在场**。售货机的好处正是「无人值守」：半夜三点、流水线里、批量一百台，它都照跑不误。

落到真实场景，`codex exec` 最常出现在这几个地方：

- **「CI 跑挂了，自动定位失败用例、提个修复补丁」**——流水线里没活人，全靠它自己跑完
- **「给最近 10 个提交生成 release notes，写进文件」**——重复活，写进脚本一键出
- **「把这段报错日志管道喂给它，让它吐根因 + 下一步排查建议」**——一行命令的事，比复制粘贴进交互界面快多了

> 💡 一句话总结：`codex exec` 是为**「没活人盯着」的场景**而生（像自动售货机无人值守）——重复活、无图形界面的环境、要把输出喂给下一个程序，这三类活儿交给它，而不是开交互界面干等。

---

## 02 一上手：最简单的样子长这样

别被「非交互」「CI」这些词吓住，`codex exec` 最基础的用法朴素得很——**`codex exec` 后面跟一句话当提示词，回车，它就开干**。

```bash
codex exec "总结这个仓库的结构，列出最该警惕的 5 个地方"
```

它会读当前工作目录、定个计划、把过程往屏幕上滚，**最后把那条总结打出来，然后退出**——不进 TUI、不停在界面等你按键。这就是「非交互」三个字最直白的含义：**跑完就走，不跟你来回聊。**

**类比：发一封带任务的邮件，等一封回信。** 交互模式像打电话——你来我往、随时插话。`codex exec` 像发邮件——你把要办的事在一封邮件里写清楚（提示词），发出去，对方关起门把事办完，回你一封信（最终结果）。**你没法在它办事的中途插嘴改需求**，所以提示词得一次性写明白。

几个你立刻能上手的变体：

```bash
# 短别名 codex e，跟 codex exec 完全等价
codex e "解释这个项目是干什么的"
```

```bash
# 换个模型跑这一次（模型名以你本地实际为准，别照抄）
codex exec -m gpt-5.5 "审查当前改动，列出潜在 bug"
```

```bash
# 不想把这次会话记录落盘，加 --ephemeral
codex exec --ephemeral "快速过一遍这个仓库，给点下一步建议"
```

这里要敲黑板的一点，**和交互模式的最大区别**：交互模式里它干到一半要动手会停下来问你「批不批准」；`codex exec` 是为「没活人」设计的，**所以它不会在中途弹出审批等你点头**。这既是它的好处（无人值守），也是它的风险点——它能动多大，全看你**启动时**给的沙箱档位。这一点第 04 节专门讲，先记住「它不会停下来问你」这件事。

> ⚠️ **一个 Git 仓库的硬性前提**：Codex **硬性要求命令在一个 Git 仓库里跑**（可用 `--skip-git-repo-check` 覆盖），这是为了防止它在没有版本管理的地方造成不可逆的破坏。要是你在一个非 Git 目录里跑 `codex exec`，它会直接拦下来。临时目录里想跑，加 `--skip-git-repo-check` 跳过这道检查——但官方原话是「**只在你确信环境安全时**」才这么干。

> 💡 一句话总结：`codex exec "一句话"` 就是最基础的非交互跑法（短名 `codex e`），像发一封带任务的邮件等回信——提示词得一次写清，它跑完就退、**中途不停下来问你批不批准**；还有个硬前提：硬性要求在 Git 仓库里跑，非 Git 目录加 `--skip-git-repo-check` 覆盖。

---

## 03 最关键的设计：进度走 stderr，结果走 stdout

这一节是整篇最该吃透的一块，**也是 `codex exec` 能优雅接进管道的命门**。

先抛个问题：Codex 跑的过程中会输出一大堆东西——它在想啥、跑了哪条命令、改了哪个文件……可你真正想要的，往往只是**最后那条总结**。要是这些全混在一起打出来，你想「把结果写进文件」时，文件里就塞满了过程噪音，根本没法用。

Codex 的解法非常干净：**把两种输出分到两条不同的「水管」里**。

**过程噪音走 `stderr`（标准错误），最终结果走 `stdout`（标准输出）。**这俩是 Unix 命令行的两条独立输出流，平时你在终端看它们混在一起，但**管道 `|` 和重定向 `>` 默认只接 stdout**。所以这一刀切下去，你想捞结果就格外省事。

**类比：工地上「施工噪音」和「最终交付的房子」走两个口子。** 盖房子的过程吵得很——打桩、搅拌、敲打（这些是 stderr，你能听见、但不是你要的成品）；最后交付到你手里的是一栋干净的房子（这是 stdout，你真正要的东西）。施工噪音从工地侧门散出去，成品从正门交付——**两个口子分开，你接「成品」的时候不会被一堆噪音灌进来**。

看它怎么省事。下面这条把最终结果**既打到屏幕、又存进文件**（`tee` 的作用），而过程噪音照常在屏幕上滚、但**不会进文件**：

```bash
codex exec "给最近 10 个提交生成发布说明" | tee release-notes.md
```

**预期**：你在屏幕上能看到 Codex 干活的过程（这些是 stderr），跑完最终的发布说明打出来；同时 `release-notes.md` 里**只有那份干净的发布说明**，没有半点过程噪音。

理解这个分流，下面这些常见操作就都顺理成章了：

| 你想干的事 | 怎么写 | 原理 |
|---|---|---|
| 只把**结果**存进文件 | `codex exec "..." > out.md` | `>` 只接 stdout（结果），stderr 照样在屏幕滚 |
| 结果**既存又看** | `codex exec "..." \| tee out.md` | `tee` 把 stdout 一份存盘、一份打屏 |
| 结果管道喂给下一个程序 | `codex exec "..." \| pbcopy` | 下游只收到 stdout 那份干净结果 |
| 把过程噪音也一起留存 | `codex exec "..." > out.md 2> log.txt` | `2>` 单独把 stderr 收进另一个文件 |

我自己第一次写脚本时栽过一个跟这有关的小跟头：当时想「把 Codex 的输出收进文件」，结果用了 `2>&1` 把 stderr 也并进了 stdout，文件里全是它「正在思考」「正在读取文件」的过程行，最终那段总结被淹在里头，下游解析直接乱套。**后来才反应过来——人家专门把结果和噪音分开了，我又手贱把它们合回去了**。把 `2>&1` 去掉，文件立刻就干净了。

> 💡 一句话总结：`codex exec` 把**过程噪音打到 stderr、最终结果打到 stdout**（像工地的施工侧门和交付正门分开），所以 `>` / `tee` / 管道默认只接到那份干净结果；别手贱用 `2>&1` 把噪音并回 stdout，否则文件全是过程行。

---

## 04 权限与安全：非交互模式默认是「只读」

上一节讲了输出怎么分流，这一节讲个更要紧的：**`codex exec` 没活人盯着，那它到底能动多大？**

先给结论，这也是最容易想当然写错的一点：**`codex exec` 默认跑在只读沙箱里**——它能读、能分析、能给建议，但**默认不会改你的文件、也不会执行有副作用的命令**。要让它真动手改东西，你得**显式放开**。

为啥默认这么保守？因为它是为「无人值守」设计的——**没人在旁边喊停**。要是默认就放开写权限、又没人盯着，一个理解偏差就可能在你仓库里乱改一通。所以官方的设计哲学是：**自动化里，给出工作流所需的最小权限就好。**

**类比：把家门钥匙交给上门服务的师傅。** 你不在家，让维修师傅上门——你不会把整套钥匙连保险柜密码一起给他。默认只给他进客厅看一眼的权限（只读）；确实要他修水管，才**单独**给他厨房和卫生间的钥匙（`workspace-write`，只在工作区里动手）；除非是封闭隔离的场子，否则绝不给他「全屋随便动、连邻居家也能进」那种万能钥匙（`danger-full-access`）。**权限给得越克制，无人值守才越放心。**

放开权限就靠 `--sandbox`（短名 `-s`）这个开关，三档对照如下：

| 沙箱档位 | 它能干啥 | 啥时候用 |
|---|---|---|
| `read-only`（**exec 默认**） | 只读、只分析，不改文件、不跑有副作用的命令 | 代码审查、生成总结、分析报告——只产出文字 |
| `workspace-write` | 在**工作区目录内**读写、动手改 | 要它真改代码 / 修 bug / 写文件，但限定在项目里 |
| `danger-full-access` | 几乎不设限，能动整台机器 | **只在隔离环境**（独立 CI runner、容器）里用 |

落到命令上：

```bash
# 默认只读：审查改动、只出报告，碰不到你的文件
codex exec "审查当前改动，列出潜在 bug"
```

```bash
# 放开工作区写权限：让它真去修，但只在项目目录内动手
codex exec --sandbox workspace-write "修好失败的测试用例"
```

```bash
# 仅限隔离环境：几乎不设限
codex exec --sandbox danger-full-access "<在隔离 runner 里的任务>"
```

几个一定要单独点清楚的坑：

**坑一：`--full-auto` 别再用了。** 老脚本里你可能见过 `codex exec --full-auto`，它现在是**已弃用的兼容标志**，用了 Codex 会打一条警告。官方明说：新脚本里直接用 `--sandbox workspace-write` 替代它，意图更明确。

**坑二：`danger-full-access` 只在隔离环境用。** 这档几乎等于「随便动」，官方原话是「**只在受控环境**（比如隔离的 CI runner 或容器）里用」。在你自己日常的开发机上对着重要项目甩这一档，跟把万能钥匙交出去没区别。

**坑三：要让自动化「干净启动」，还有两个忽略开关。** `--ignore-user-config` 让这次跑**不加载** `$CODEX_HOME/config.toml`（避免被本机或队友的个人配置干扰）；`--ignore-rules` 让它**跳过**用户级和项目级的 execpolicy `.rules` 文件。这俩是给「受控自动化环境」用的，想要每台机器跑出来行为一致时用得上。

我去年配一个 CI 任务时就吃过「权限想当然」的亏：以为 `codex exec` 跟我交互时用的是同一套配置（我本地常开着写权限），结果它在 CI 里跑了半天啥也没改——一脸懵。翻文档才明白，**非交互模式默认就是只读，跟我交互时的环境是两码事**。补上 `--sandbox workspace-write` 之后它才真动手。**这个默认值，新手十有八九会踩。**

> 💡 一句话总结：`codex exec` **默认只读沙箱**（像只给上门师傅进客厅的权限），要改文件得显式 `--sandbox workspace-write`、几乎不设限的 `danger-full-access` 只在隔离环境用；老的 `--full-auto` 已弃用别再写，受控自动化还能用 `--ignore-user-config` / `--ignore-rules` 干净启动。

---

## 05 让输出机器可读：`--json` 与 `-o` 这对搭档

第 03 节解决了「结果怎么不被噪音污染」，但还有个更进一步的问题：**脚本要解析 Codex 的输出，纯文本不好啃**。比如你想知道「它这次跑成功了没」「跑了哪几条命令」「花了多少 token」，从一段自然语言里抠这些信息既别扭又不稳。

Codex 给了两个互补的工具，**新手务必分清它俩管的不是一件事**。

**第一个，`--json`：把整个过程变成机器能读的事件流。** 加上 `--json`（官方还有个等价别名 `--experimental-json`），`stdout` 就从「一段自然语言」变成 **JSON Lines（JSONL，每行一个独立 JSON 对象）的事件流**——Codex 干活时每发生一个状态变化，就吐一行 JSON。

**类比：从「看一场演出」变成「拿到一份带时间戳的演出流水账」。** 不加 `--json` 像现场看演出——好看，但散场你说不清「第几分钟换了第几个节目」。加了 `--json` 像拿到一份逐条记录的流水账：几点几分开场、第几个节目上、几点谢幕——**每一步都是结构化的一条，程序能逐行读、逐条判断**。

```bash
codex exec --json "总结这个仓库的结构" | jq
```

它吐出来的每一行都是一个事件对象，类型包括 `thread.started`（线程开始）、`turn.started` / `turn.completed` / `turn.failed`（一轮的开始 / 完成 / 失败）、`item.*`（具体动作，如执行命令、改文件、调用 MCP 工具、联网搜索、更新计划等）、以及 `error`。长这样（每行是一个独立 JSON）：

```jsonl
{"type":"thread.started","thread_id":"0199a213-81c0-7800-8aa1-bbab2a035a53"}
{"type":"turn.started"}
{"type":"item.completed","item":{"id":"item_3","type":"agent_message","text":"仓库包含 docs、sdk、examples 三个目录。"}}
{"type":"turn.completed","usage":{"input_tokens":24763,"output_tokens":122}}
```

有了这个，脚本里判断成功失败、提取它干了哪些事、统计 token 用量，全都有结构化字段可抓。

**第二个，`-o` / `--output-last-message`：只把最终那条消息单独存一份。** 很多时候你不关心过程流水账，只想要**最后那段总结**，落进一个文件好给下一步用。加 `-o <文件路径>`（长名 `--output-last-message`）就行：

```bash
codex exec "提炼项目元信息" -o ./summary.md
```

这里有个**官方明确写了、但容易忽略的细节**：`-o` 会把最终消息写进那个文件，**同时还照常打到 stdout**——也就是说它不抢 stdout，你想再管道接给别人也不耽误。

那这俩到底咋选？一张表说清：

| 你想要的 | 用哪个 | 形态 |
|---|---|---|
| 程序逐步读它的每个动作 / 判断成败 / 统计用量 | `--json` | stdout 变成 JSONL 事件流 |
| 只要最终那段总结，落一个文件 | `-o <path>` | 最终消息写进文件，**且仍打到 stdout** |
| CI 里两样都要 | **`--json` + `-o` 一起上** | 事件流喂程序，文件捞干净的最终总结 |

最后这条是官方专门推荐的 CI 黄金搭配——**在 CI 里把 `--json` 和 `--output-last-message` 配在一起，既拿到机器可读的进度，又拿到一份最终的自然语言总结。**

我自己一个每日代码审查的脚本就是这么配的：`--json` 那条流喂给一个小解析器，判断「这次有没有发现高危问题、跑成功没」来决定要不要告警；`-o` 落的那份 Markdown 总结，则直接贴进团队群当「今日审查摘要」。**一次跑，机器要的和人要的两头都齐了**，再没有从一坨文本里抠字段的别扭。

还有个更进阶的 `--output-schema`：给它一个 JSON Schema 文件，**让最终结果严格按你定义的字段结构产出**（比如固定的「项目名 + 语言列表」这种），下游程序拿到的字段稳定可靠。需要结构化数据喂给后续步骤时再深入，本篇先知道有这么个东西。

> 💡 一句话总结：`--json` 把 stdout 变成 **JSONL 事件流**（像一份逐条流水账，程序好逐行读、判断成败统计用量），`-o` / `--output-last-message` 把**最终那段总结单独落一个文件且仍打到 stdout**；CI 里官方推荐**两个一起上**——机器要的和人要的一次拿齐。

---

## 06 stdin 管道：把上一个命令的输出直接喂给它

到这儿你已经会「让 `codex exec` 的输出接给下游」了。反过来——**怎么把上一个命令的输出，喂给 `codex exec` 当输入？** 这就是 stdin（标准输入）管道，也是 `codex exec` 在命令行工作流里最顺手的一招。

为啥需要它？因为太多真实场景是「**先有一坨数据，再让 Codex 处理它**」：构建挂了有一段报错日志、一次请求拿到一段 JSON、CI 跑完有一段输出……你不想手动复制粘贴进交互界面，而是想一行命令直接「管道」过去。

这里有个**新手特别容易混的分叉**，官方把两种姿势分得很清楚，我用一句话先点破区别：**指令是你写好的、数据当上下文 → 用「提示词 + 管道」；整个提示词都由上一个命令动态生成 → 用 `codex exec -`。**

**类比：给秘书交代任务的两种方式。** 第一种，你口头交代「把这份材料整理成表格」，同时把一摞材料递过去——**指令在你嘴里，材料是附件**（提示词 + 管道）。第二种，你把一张写好全部要求的纸条直接递过去，说「照这上面办」——**连指令带内容全在纸条上**（`codex exec -`）。两种都行，看你的「指令」是现写的、还是早就写在「纸条」（上游命令的输出）里了。

### 姿势一：提示词 + 管道（指令你写，管道内容当上下文）

**当你心里已经清楚要它干啥**，只是想把某个命令的输出当材料喂进去时用这个。官方的规则很明确：

> 如果 stdin 是管道进来的，同时你又给了提示词参数，Codex 会**把提示词当指令、把管道内容当额外上下文**。

最典型的——构建/测试挂了，把报错管道喂给它要根因：

```bash
npm test 2>&1 \
  | codex exec "总结失败的测试，提出最小改动的修复方案" \
  | tee test-summary.md
```

**预期**：`npm test` 的全部输出（`2>&1` 把报错也并进来）作为上下文喂给 Codex，你写的那句话作为指令；Codex 的总结打到屏幕、同时存进 `test-summary.md`。排查构建挂掉时，就这么一行甩过去，比复制报错粘进会话快太多。

再比如把一段长日志喂给它做根因分析：

```bash
tail -n 200 app.log \
  | codex exec "找出最可能的根因，引用最关键的几条报错，给出接下来三步排查建议" \
  > log-triage.md
```

### 姿势二：`codex exec -`（让 stdin 当整个提示词）

**当整个提示词都是上一个命令动态拼出来的**——比如提示词存在文件里、或用脚本现拼——这时候你不想自己再写指令，而是让 stdin 的内容**直接当成完整提示词**。省略提示词参数时 Codex 就会从 stdin 读；想强制这个行为、写清楚意图，就用 `-` 这个「哨兵」符号：

```bash
# 把一个文件里的内容当成整个提示词
cat prompt.txt | codex exec -
```

```bash
# 用脚本现拼一个完整提示词，再整个喂进去
printf "用 3 条要点总结这段错误日志：\n\n%s\n" "$(tail -n 200 app.log)" \
  | codex exec -
```

什么时候非得用 `-`？当你**把提示词存成文件、或用 shell 脚本组装提示词、或把实时命令输出和指令拼成一整段**再丢给 Codex 时——这几种都是 `codex exec -` 的主场。

我自己维护着几个「提示词模板文件」（比如一个标准的 PR 审查提示词），需要时 `cat 模板.txt | codex exec -` 直接跑，改提示词只改那个文件、不用动脚本。**指令和脚本解耦，这点在团队里特别值。**

> 💡 一句话总结：喂数据给 `codex exec` 看「指令从哪来」——**指令你现写、管道内容当上下文** 用「提示词 + 管道」（像口头交代任务 + 递材料）；**整个提示词由上游动态生成** 用 `codex exec -`（像直接递一张写好全部要求的纸条）。

---

## 07 接着上次跑：`codex exec resume`

非交互不代表「只能一锤子买卖」。有时候一个流程得分两段跑——**先让它分析，再基于分析结果让它动手**。这种「两阶段流水线」，靠 `resume` 子命令把前后两次串起来。

**类比：接力赛的交接棒。** 第一棒跑完（第一次 `codex exec`）把棒子交出去，第二棒接着这个位置往下跑（`codex exec resume`）——**不用从起点重来，上一棒的上下文直接接上**。要是没有 resume，第二次跑就得把第一次的结论重新喂一遍，既费 token 又容易丢细节。

最常见的两阶段写法，官方给的例子：

```bash
# 第一阶段：先让它找问题
codex exec "审查这处改动有没有竞态条件"

# 第二阶段：接着上一次，让它修掉刚找到的问题
codex exec resume --last "把你发现的竞态条件修掉"
```

`--last` 的意思是「接着**当前工作目录下最近的那次**会话」。要是你想精确接某一次特定会话，把会话 ID 跟在后面：

```bash
codex exec resume <SESSION_ID> "继续上次的任务"
```

补两个细节：想跨目录找最近的会话，加 `--all`；`resume` 后面的提示词是**可选的**——你可以只 resume 不带新指令，也可以带一句新指令接着干。

> 注意：第 02 节说过 `--ephemeral` 不把会话落盘，那种「阅后即焚」的跑法自然就**没法 resume**（都没存，接什么）。要用两阶段流水线，就别加 `--ephemeral`。

> 💡 一句话总结：`codex exec resume --last` 接着当前目录最近一次会话往下跑（像接力赛交接棒，不用从起点重来），也能用会话 ID 精确接某次；但加了 `--ephemeral` 不落盘的跑法没法 resume。

---

## 08 动手：把「跑一次 → 拿结果 → 批量跑」走通

光看不练明天就忘。下面给一套**最小验证流程**，在任意一个 Git 仓库里就能跟着走（没有就先 `git init` 一个空目录）。命令真实可跑，每步都给了预期输出。

> 需要装好 Codex CLI（没装回第 03 篇）、且**在一个 Git 仓库里**跑。下面命令不依赖魔法上网。涉及的模型名 / 具体输出以你本地实际为准。

**第一步：跑最基础的一次（只读，不会动你文件）**

```bash
codex exec "用一句话说清这个项目是干什么的"
```

**预期**：屏幕上滚一阵过程（这些走 stderr），最后打出一句话总结（这条走 stdout），然后**自动退出回到普通终端**——不进交互界面。看到「跑完就退」= 非交互模式生效了。

**第二步：把结果存进文件，验证 stdout / stderr 分流**

```bash
codex exec "用一句话说清这个项目是干什么的" > result.txt
```

**预期**：屏幕上**还是能看到过程**（stderr 没被重定向），但跑完去看 `result.txt`，里面**只有那句干净的总结**，没有过程噪音。这就证明了第 03 节讲的分流——`>` 只接住了 stdout。

**第三步：加 `--json` 看机器可读的事件流**

```bash
codex exec --json "列出这个项目里的文件类型"
```

**预期**：stdout 变成一行行 JSON（JSONL），开头能看到 `{"type":"thread.started",...}`，中间是 `item.*` 各种动作，结尾通常是 `turn.completed` 带 token 用量。看到这串逐行 JSON = 机器可读模式生效。（装了 `jq` 的话，在后面接 ` | jq` 看得更清楚。）

**第四步：用 `-o` 把最终消息单独落一份**

```bash
codex exec "用一句话总结这个项目" -o last.md
```

**预期**：屏幕照常打出那句总结（stdout 没被抢），**同时** `last.md` 里也存了同一句话。验证了 `-o` 既写文件、又仍打 stdout。

**第五步：把它拼成「批量」——对一批文件各跑一遍**

真正的批量，是拿 shell 循环把单条 `codex exec` 套起来。比如给当前目录每个 `.md` 文件各生成一句话说明（`--ignore-user-config` 让每次干净启动、行为一致）：

```bash
for f in *.md; do
  echo "=== $f ==="
  codex exec --ignore-user-config "用一句话说明 $f 写的是什么"
done
```

**预期**：循环对每个 `.md` 文件各跑一次 `codex exec`，逐个打印文件名 + 它的一句话说明。**这就是「无人值守批量」的雏形——循环 + `codex exec`，再没有活人盯着。**

跑通这五步，你就把「跑一次 → 看分流 → 拿 JSON → 落文件 → 批量套循环」这条非交互主链亲手走了一遍。**以后真要塞进脚本或 CI，本质都是这套，无非换成真实任务、加上 `--sandbox workspace-write` 让它动手、再接进流水线。**

> 💡 一句话总结：动手链路就五步——`codex exec` 跑一次 → `>` 存文件看分流 → `--json` 拿事件流 → `-o` 落最终消息 → `for` 循环拼批量；走通一遍，「把 Codex 当脚本零件」这件事你就有肌肉记忆了。

---

## 09 小结

这一篇把 `codex exec` 非交互模式，从「它为谁而生」一路讲到「怎么塞进脚本批量跑」，中间最该带走的是那几个「想当然会写错」的点。

把核心收成一张表，揣兜里：

| 你想干的事 | 怎么做 | 关键点 |
|---|---|---|
| 跑一次非交互任务 | `codex exec "..."`（短名 `codex e`） | 跑完就退、中途不停下来问你批准 |
| 只把干净结果存文件 | `codex exec "..." > out.md` | 结果走 stdout、过程噪音走 stderr，`>` 只接结果 |
| 让它真改文件 | 加 `--sandbox workspace-write` | **默认是只读**，不显式放开它不动手 |
| 拿机器可读的过程 | `--json` | stdout 变 JSONL 事件流，程序好逐行读 |
| 捞最终总结到文件 | `-o <path>` | 写文件且**仍打到 stdout**；CI 里和 `--json` 配着用 |
| 把上游输出喂给它 | 提示词 + 管道 / `codex exec -` | 看指令是你现写的、还是上游动态生成的 |
| 接着上次跑 | `codex exec resume --last "..."` | 两阶段流水线；`--ephemeral` 没法 resume |

**你现在应该能**：讲清 `codex exec` 专治哪几类「没活人」的场景；看懂「进度走 stderr、结果走 stdout」这一刀切开后，管道和重定向为啥那么顺；分清 `--json` 和 `-o` 各管啥、CI 里为啥要一起上；记住那个最容易栽的默认值——**非交互模式默认只读，要动手得显式 `--sandbox workspace-write`**；还能用两种 stdin 姿势把上游数据喂进去、用 `resume` 串起两阶段流程。

**最该带走的一句话**：`codex exec` 不是「阉割版的交互模式」，而是**让 Codex 从一个你盯着用的工具，变成你工作流里一个能被脚本、管道、CI 自由调用的标准零件**——这一步迈过去，自动化的大门才算真打开。

---

下一篇 **〔[29 Slack / Linear 与 SDK 集成](29-integrations.md) 〕**：这一篇你已经会把 Codex 接进脚本和 CI 了，但还都是「命令行里」的接法。下一篇换个方向——把它接进你**天天用的协作工具**：在 Slack 里 @ 一下就让它干活、Linear 上的工单自动派给它、或者用 SDK 把它的能力嵌进你自己的程序里。想想看：`codex exec` 把输出管道给了 `gh pr comment`，那把它接到 Slack、接到工单系统，中间又少了哪几步手动操作？咱们下篇接着聊。
