本文基于 codex 仓库当前代码结构与仓库规范整理,描述的是该仓库的一种典型开发工作流,不代表所有 Codex 项目都完全一致 假设你今天刚拿到 Codex 的代码权限,mentor 在 Slack 上丢给你一个需求:
"给 TUI 的 composer 增加一个粘贴 burst 模式------当用户一次性粘贴多行文本时,不要把 Enter 键识别为提交,而是当作换行符,等粘贴结束后再统一提交。"
你打开仓库,看到了 docs/、codex-rs/、.codex/skills/、tools/......目录很多,但你不知道该从哪下手。
本文就是这个场景下的操作手册。
第一步:判断要不要写设计文档
Codex 没有"所有功能都必须写设计文档"的规定。你的判断标准是:
这个子系统的交互复杂度,是否超出了代码本身能表达的范围?
对于"粘贴 burst 模式"这个需求:
- 它涉及 TUI 输入事件的处理顺序(键盘事件 → composer 状态机 → paste detection)
- 它有一个边界条件:非 ASCII 字符和 ASCII 字符的处理方式不同
- 它会影响现有的
Enter键提交行为
→ 需要写设计文档。
但如果你的需求只是"给某个配置项增加一个新选项",直接写代码 + 内联注释就够了。
接到需求
│
▼
是否涉及多模块协调 / 复杂状态机 / 边界条件?
│
├── 是 → 写 docs/*-design.md
│
└── 否 → 直接写代码 + rustdoc
第二步:写设计文档,同时列出 "Tests that pin behavior"
Codex 的设计文档不是"写完就放下"。它有一个固定套路:
- 问题定义:用户粘贴多行时,Enter 被误识别为提交
- 设计目标:区分"paste burst"和"正常打字"
- 非目标:不支持超过 1000 字符的 burst(超出范围)
- 实现方案 :在
ChatComposer中增加PasteBurst状态机 - Tests that pin behavior :-非 ASCII 内容的粘贴 burst 能正确处理多行换行,且不会在中途把字符显示出来
non_ascii_burst_buffers_enter_and_flushes_multilineascii_burst_treats_enter_as_newlinequestion_mark_does_not_toggle_during_paste_burst
这一步的关键是:你还没写一行代码,但已经知道哪些行为必须被测试锁定。 这些测试名不是随便起的,它们对应设计文档里的每一个关键决策。
bash
docs/paste-burst-design.md
│
├── 问题定义
├── 设计目标 / 非目标
├── 实现方案
└── Tests that pin behavior ← 这一节必须有
第三步:写代码和测试,不是先写哪个,而是并排写
Codex 没有要求你先写测试再写代码。实际工作流是:设计文档定好后,代码和测试并排迭代。
3.1 代码放哪里
你的改动会涉及:
codex-rs/tui/src/bottom_pane/chat_composer.rs------ 核心实现codex-rs/tui/src/bottom_pane/AGENTS.md------ 更新模块级规范(如果需要)
写代码时要遵循 AGENTS.md 的规范。根目录的 AGENTS.md(截至 2026/04 约 17KB)规定了全仓库的通用规则,而 bottom_pane/AGENTS.md 规定了该模块的特殊要求------比如"keep the docs in sync"。
3.2 测试怎么写
你的测试会写进两个地方:
集成测试 (必须):codex-rs/core/tests/suite/ 或 codex-rs/tui/tests/suite/
rust
#[tokio::test]
async fn paste_burst_does_not_submit_on_enter() {
let codex = test_codex().build(&server).await;
codex.submit_turn("paste some multiline text").await;
// 验证 composer 没有触发提交
}
单元测试 (按需):放在源码文件里的 #[test] 或 *_tests.rs
rust
#[test]
fn non_ascii_burst_buffers_enter_and_flushes_multiline() {
let mut composer = ChatComposer::new(...);
// 模拟 paste burst,验证 Enter 被当作换行
}
Codex 的 code-review-testing skill 明确规定:agent 逻辑变更必须加集成测试。 不是"尽量加",是 MUST。
3.3 本地验证循环
你的本地开发循环应该是:
bash
# 1. 格式化(imports 粒度、换行等)
just fmt
# 2. 自动修复 + lint 检查
# 这背后会跑:cargo clippy(代码规范)、argument-comment-lint(参数注释匹配)、
# codespell(拼写检查)、cargo-shear(死依赖检测)
just fix -p codex-tui
# 3. 运行你修改的测试
cargo test -p codex-tui paste_burst
# 4. 全量测试(如果你改了公共模块)
just test
关键原则:lint 不是 CI 的惩罚,是本地就该拦截的纠错。 如果在 just fix 阶段暴露拼写错误或参数注释不匹配,比 push 之后被 CI 打回来快 10 倍。
bash
写代码 ──→ 写测试 ──→ just fmt ──→ just fix ──→ cargo test
▲ │
└──────────── 失败则回修 ───────────────────┘
第四步:安全沙箱------你的命令会被拦下来
当你运行 cargo test 时,Codex 的测试会 spawn 子进程执行 shell 命令。这时你会遇到 Codex 的安全机制:
- 进程加固:Codex 进程已经禁用了 ptrace 和 core dumps
- 平台沙箱:测试中的 shell 命令在 bubblewrap(Linux)或 Seatbelt(macOS)中运行
- Approval:如果测试试图执行敏感操作,会弹窗请求批准
- 网络代理:测试中的网络请求走本地 SOCKS 代理,按策略放行
你不需要特别处理什么,只需要知道:如果测试因为沙箱被拒绝,错误信息会告诉你缺少什么权限。
常见的沙箱失败信号:
SandboxErr::Denied------ 沙箱拒绝了文件写入或网络访问permission denied in sandbox------ 命令试图访问沙箱外的路径- 测试挂起直到超时 ------ 可能是网络请求被 SOCKS 代理阻塞,等待审批
排查方法:
-
先看错误信息里被拒绝的具体路径/操作
-
检查
codex-rs/sandboxing/中的权限配置 -
如果是远程环境不支持的测试,用
skip_if_remote!宏跳过 -
在
nextest.toml里调整slow-timeout避免挂起测试拖慢整体进度运行测试
│
▼
测试失败
│
▼
错误信息含 SandboxErr / permission denied?
│
├── 是 → 查看具体被拒绝的路径/操作
│ → 调整沙箱权限或 skip_if_remote!
│
└── 否 → 普通代码 bug,按常规方式修复
第五步:提交 PR------PR Body 有规范
代码和测试都通过后,你要提交 PR。Codex 有一个 codex-pr-body skill 定义了 PR Body 的写法:
- 先解释 Why,再解释 What
- 保留作者原有内容(不要删掉别人放的截图)
- 讨论 net change,不要写"我试了什么但放弃了"
- 用 Markdown 格式,代码用反引号
- 引用相关 issue / PR
PR description 的模板很简单------What / Why / How。
markdown
## What
增加 PasteBurst 状态机,区分 paste burst 和普通打字。
## Why
用户粘贴多行文本时,Enter 被误识别为提交,导致指令提前执行。
## How
- 在 ChatComposer 中增加 PasteBurst 状态
- 非 ASCII burst 时缓冲 Enter 作为换行
- ASCII burst 时同样处理,但保持与现有行为的兼容
## Tests
- `non_ascii_burst_buffers_enter_and_flushes_multiline`
- `ascii_burst_treats_enter_as_newline`
第六步:CI 流程------你的 PR 会经历什么
提交 PR 后,CI 会分两层跑:
PR 级快速检查(rust-ci.yml)
- 路径感知 :只跑和你修改相关的检查
- 改了
codex-rs/*→ 跑cargo fmt、cargo-shear - 改了
codex-rs/tui/*→ 跑 TUI 相关测试
- 改了
- 速度优先:不跑完整测试矩阵
- 失败阻断:任何 lint 失败都会阻断合并
全量 CI(rust-ci-full.yml)
- 在
push到main或**full-ci**分支时触发 - tests job 用 45 分钟超时(其他 job 30 分钟)
- 跑完整集成测试矩阵
scss
提交 PR
│
▼
┌─────────────────┐
│ rust-ci.yml │ ← 路径感知,快速反馈
│ (PR 级) │
└─────────────────┘
│
▼
合并到 main
│
▼
┌─────────────────┐
│ rust-ci-full.yml│ ← 全量测试,45min 超时
│ (Full CI) │
└─────────────────┘
第七步:PR Babysitter------提交后还有人帮你盯着
PR 提交后,babysit-pr skill 会自动:
- 监控 CI 状态
- 如果失败,区分是"代码问题"还是"flaky 环境问题"
- 对 flaky 失败自动重跑:
python3 .codex/skills/babysit-pr/scripts/gh_pr_watch.py --pr auto --retry-failed-now
这意味着你不需要每隔 10 分钟刷新 CI 页面------Codex 会自己 babysit。
第八步:文档同步------别忘了 AGENTS.md 的 checklist
你的代码合并后,还有一件事:检查文档是否同步。
codex-rs/tui/src/bottom_pane/AGENTS.md 里写了:
"keep the docs in sync: Update the relevant module docs... Update the narrative doc
docs/tui-chat-composer.mdwhenever behavior/assumptions change..."
这意味着当你修改了 ChatComposer 的行为时,你必须同时更新:
- 模块级 rustdoc(源码里的注释)
- 用户文档(
docs/tui-chat-composer.md) - 设计文档(如果行为/假设变了)
除非你有意保持不一致,并且记录了原因。
代码合并
│
▼
是否需要更新文档?
│
├── 是 → 更新 rustdoc + 用户文档 + 设计文档
│
└── 否 → 确认 divergence 是有意且已记录的
完整流程图(决策视角)
bash
接到需求
│
┌───────────┴───────────┐
│ │
涉及多模块/复杂状态机? 简单配置变更?
│ │
▼ ▼
写 docs/*-design.md 直接写代码
└── Tests that pin behavior └── rustdoc
│
▼
┌───────────────┐
│ 写代码 + 测试 │
│(并排迭代) │
└───────────────┘
│
▼
just fmt → just fix
└── argument-comment-lint
└── codespell / cargo-shear
│
▼
cargo test 通过?
│
┌───────┴───────┐
│ │
▼ ▼
通过 沙箱拦截?
│
┌───────┴───────┐
│ │
▼ ▼
调整权限/ 代码 bug
skip_if_remote! │
│ │
└───────┬───────┘
│
▼
提交 PR
└── What / Why / How
│
▼
rust-ci.yml 通过?
│
┌───────┴───────┐
│ │
▼ ▼
通过 lint 失败?
│
▼
just fmt/fix 本地重跑
│
▼
合并到 main
│
▼
rust-ci-full.yml
│
▼
是否需要更新文档?
│
┌─────────┴─────────┐
│ │
▼ ▼
行为变了? 无变化
│ │
▼ ▼
更新 rustdoc + 用户文档 完成
+ 设计文档
│
▼
完成
新人最容易踩的三个坑
坑一:忘了集成测试
Codex 的 code-review-testing skill 说得很清楚:agent 逻辑变更 MUST 加集成测试。 不是"尽量",不是"建议",是强制。如果你只写了单元测试,reviewer 会要求你补集成测试。
坑二:改了代码没更新 AGENTS.md
AGENTS.md 不是摆设。它会被加载到 AI agent 的上下文中,agent 生成代码时会自动遵循。如果你改了模块规范但没更新 AGENTS.md,agent 以后生成的代码就会和你新的实现不一致。
避免方法: 提交前过一遍三层文档检查清单------
- 源码 rustdoc:新行为是否有注释说明?函数签名变更是否更新了 docstring?
- 用户文档 :
docs/下的相关文档是否需要补充新功能说明? - 设计文档 :如果行为/假设变了,
Tests that pin behavior是否需要追加新测试名?
坑三:snapshot 测试挂了不知道怎么办
TUI 改动最容易触发 snapshot 测试失败。如果你看到 .snap.new 文件:
- 打开
.snap.new文件,人工确认变化是否符合预期 - 如果符合,执行
cargo insta accept(需先安装cargo insta) - 把
.snap文件的变更一起提交到 PR
这不是测试坏了,而是 TUI 渲染输出变了------ Codex 用这种方式让 UI 变化可 review。
可学习点
- 设计先行但务实:复杂功能先写设计文档,简单功能直接写代码
- 测试不是补的,是一起写的:设计文档定稿时就应该列出 Tests that pin behavior
- AGENTS.md 是活契约:改了代码要同步更新,否则 agent 会按旧规范生成代码
- lint 不是惩罚,是自动纠错 :
just fmt和just fix应该在提交前跑,不是在 CI 挂了之后跑 - 安全是默认开启的:沙箱、审批、网络代理不需要你配置,但你需要知道它们的存在
系列定位
本文是 Codex 工程解读系列的实战补充篇。前五篇分别讲了 Skill 系统、Lint 哲学、安全架构、文档管理和测试哲学的底层原理,本文把它们串成了一条可操作的工作流。