你让 Codex「重构 billing 模块」,它返回一个动了 50 个文件、1500 行的 PR。每一处单看都说得过去,但团队没人愿意 review 1500 行 agent 写的代码。PR 挂一周、和 main 冲突,最后被关掉,留言「拆一下」。
最快的修法: 任何非琐碎 task 开工前,先把 Codex 切到 Plan Mode(用 /plan,或在 CLI / IDE 扩展里按 Shift+Tab 循环模式,截至 2026 年 6 月)。Plan Mode 只读运行,先给一份编号 plan 然后停下等你——所以你在写一行代码之前就能看到这是个 50 文件的活,可以直接说「只做 step 1」。如果大 PR 已经出来了,让 Codex 跑 /review 再拆,然后 cherry-pick 成一串小 PR(见 Step 4)。
大 PR 不是模型缺陷,是 scope 问题:任务描述本身就授权了它动这么多。持久的修法是从三层封顶 diff 大小——prompt(一个 task 一个关切)、AGENTS.md(写死的每 PR 预算)、CI(超大 diff 直接 fail)。
你属于哪一种?
| diff 里的症状 | 大概率原因 | 跳到 |
|---|---|---|
| 一片散乱改动,没有单一核心 | 开放式 task(「重构 X」) | 原因 1,Step 1-2 |
| 小核心改动 + 一圈「反正路过」halo | 顺手的相邻清理 | 原因 2,Step 3 |
| PR 描述里有 4+ 个不相关 bullet | 混了多个独立关切 | 原因 4,Step 1、4 |
| 小改动 + 大量雷同的 call site 改动 | 一个签名改动级联开了 | 原因 6,Step 6 |
| 每个 task 都这样,不只这一次 | 没有 AGENTS.md 上限 / 没有 CI gate | 原因 3、5,Step 2、5 |
常见原因
按命中率从高到低。
1. Task 是开放式的(「重构 X」)
「重构 billing 模块」「优化性能」「清理 auth 层」没有自然停止点。Codex 会一直改到没有显然可改的为止,而不是到一个有意义的 chunk 就停。
如何判断: 回看 prompt。出现「refactor」「improve」「clean up」「modernize」之类没限边界的开放动词,就是你授权了无限。
2. Codex 做了「顺手」的相邻清理
原 task:修 billing.ts 里一个 typo。Codex 4 行改完,环顾一圈发现同目录 import 不一致,「顺便」在 30 个文件里 normalized 了。
如何判断: 一个小核心改动外面裹着一圈「反正路过就改」的 halo。Halo 文件不动原 bug,是为一致性改的。
3. AGENTS.md 没限 diff 大小
没有明确的「每 PR 最大 diff」规则,Codex 就把每个看似合理的修改都当 in-scope。多数团队有隐式默契,Codex 不读隐式。AGENTS.md 会在 session 开始时自动加载进上下文(repo 根目录,外加 ~/.codex 全局和按目录的文件,越具体越优先),所以写在那里的规则对每个 task 都生效。
如何判断: grep -i "diff\|pr size\|scope" AGENTS.md 没有输出——说明没封顶。
4. 重构混了多个独立关切
PR 改名一个函数、补了测试、顺手修了 3 个不相关 bug、还更新了 doc。每块都小,加一起就是 1500 行的 review 负担。
如何判断: PR description(或 commit list)有 4+ 个独立 bullet,每个本应是独立 PR。
5. Codex 没先提 plan
在普通模式下直接让它干大事,它就直接出代码——没 plan 就没机会让你说「先做 1-3、4-7 先停」。
如何判断: session 历史里代码之前没有 plan 或拆分,只有「task → 1500 行」。
6. 一行的修复意外级联
formatDate 的改动合理地需要更新 14 个 caller。Codex 把这些全打进一个 PR,而本该 PR1 = 新 formatDate 签名、PR2..N = caller 分批迁。
如何判断: 一个小「真改」加上大量近乎雷同的 call site 更新——本应拆成 PR 链。
最短修复路径
按收益从高到低。前两步在大 PR 发生之前就预防它。
Step 1:用 Plan Mode 先拿 plan 再写代码
Plan Mode 就是「先给我看 scope」的内置版本。用 /plan 切(或按 Shift+Tab 循环 Read Only → Plan → Auto)。Codex 只读探查、问澄清问题,返回一份编号 plan,你批准或编辑后它才动手。对于散乱的大 task,再贴一段明确框架,让 plan 以「可独立成 PR 的单元」返回:
写代码前先提 plan:
1. 编号列出离散步骤,每步都能独立成 PR。
2. 每步:目标文件(数量 + 路径)、估算 diff 大小、对其他步的依赖。
3. 指出能最快带来可见价值的最小那步——从那步开始。
等我批准再生成代码。
50 文件的 plan 会先给你看到,你可以回「只做 step 1」。
Step 2:在 prompt 和 AGENTS.md 里封顶 diff
官方的 prompt 框架是 Goal / Context / Constraints / Done when。把大小限制放进 Constraints:
Constraints:
- 最多动 5 个文件。
- diff 控制在 200 行内(不含测试和生成代码)。
- 不要为「一致性」动 task scope 外的文件。
- 装不下就停下来提拆分方案。
Done when: 指定改动完成且现有测试通过。
在 AGENTS.md 里(跨 session、跨模型升级都留存的持久规则):
## PR sizing
- 默认上限:每 PR 增删 200 行(不含测试 / 生成)。
- 硬上限:500 行;超过要显式批准。
- 相邻「顺手清理」独立成 PR,绝不打包。
- 重构:先提 plan,拆成 PR 链。
AGENTS.md 要稳稳控制在 project_doc_max_bytes 上限以内(截至 2026 年 6 月为 32 KiB);超过后 Codex 会截断文件,可能悄悄丢掉你的规则。
Step 3:禁掉机会主义改动
Prompt 里:
不要做任务之外的任何改动:
- 不要「反正路过就 normalize import」。
- 除非是你正在改的那几行,否则不要 format。
- 不要为一致性改名字。
- 看到其他问题在文末列成 TODO,不要顺手修。
Step 4:用 Codex 自己帮忙拆大 PR
如果 PR 已经存在、你又不想丢掉工作,先跑 /review 让 Codex(和你)看清 diff 结构,再让它拆:
这个 PR 有 50 个文件,拆成 4 个独立 PR:
1. 读 diff:`gh pr diff <number>`。
2. 按独立关切提一份 4-PR 拆分方案。
3. 每个 PR 列:文件、行数、commit message。
我之后 cherry-pick 到独立分支。
git:
# 每 PR 一个分支
git checkout main
git checkout -b pr1-rename-helpers
git cherry-pick <commits-for-pr1>
git checkout main
git checkout -b pr2-add-tests
git cherry-pick <commits-for-pr2>
# ...每个拆分重复
如果工作全在一个 squash 后的 commit 里、没有按关切分好的干净 commit,就别 cherry-pick——改用 git checkout main -- <paths> 在每个分支只 stage 该 PR 的文件。
Step 5:加一条 CI check 拦住大 PR
这个 gate 用 git diff --numstat,它会打印机器可读的 added removed path 行,比解析 --shortstat 汇总更稳:
# .github/workflows/pr-size.yml
name: PR size
on: pull_request
jobs:
size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Check PR size
run: |
ADDED=$(git diff --numstat "origin/${{ github.base_ref }}...HEAD" -- \
':!**/*.snap' ':!**/__generated__/**' \
':!**/package-lock.json' ':!**/pnpm-lock.yaml' ':!**/yarn.lock' \
| awk '{ s += $1 } END { print s + 0 }')
echo "Reviewable lines added: $ADDED"
if [ "$ADDED" -gt 500 ]; then
echo "::error::PR adds ${ADDED} lines (max 500). Split it."
exit 1
fi
pathspec 排除了 lockfile、snapshot 和生成代码,所以拦的是真正可 review 的行数。把 500 改成和你 AGENTS.md 里硬上限一致的数。
Step 6:真要大重构,走 feature flag 链
变更确实必须原子时(比如迁 API)就分阶段发:
Step 1:新路径和老路径共存(小 PR,flag 关)。
Step 2:caller 分批迁(小 PR,flag 还关)。
Step 3:翻 flag(一行 PR)。
Step 4:删老代码(小 PR)。
一个 1500 行的 PR 就变成四个约 300 行的,各自独立 review。
怎么确认修好了
- 下一个非琐碎 task 先返回 plan 而不是 diff——说明 Plan Mode(或你的 prompt)生效了。
- 在新分支上
git diff --numstat origin/main...HEAD | awk '{s+=$1} END {print s}'的结果在你的上限以内。 PR sizecheck 显示绿色(或在超大 PR 上明确 fail——两者都对)。grep -A6 "PR sizing" AGENTS.md能看到你写的上限,说明规则确实进了上下文。- reviewer 能一口气读完整个 diff,不会出现「这块我回头再看」。
预防建议
- 默认 plan 优先:任何非琐碎 task 都开 Plan Mode,批准 plan 再写代码。
AGENTS.md写硬 PR-size 上限(如默认 200、硬顶 500)——Codex 按写的来。- 每个 prompt 都关掉机会主义相邻修改:「只动指定问题」。
- CI 加 PR-size gate,超大 PR 进不了 review。
- 重构走 feature flag + 小 PR 链,不要一份原子大 PR。
- Codex 提 50 文件 plan 时推回去(「只做 step 1」),别接受整个方案。
常见问题
Codex 真有「先 plan 再写代码」的模式,还是得我自己 prompt?
两者都有。Plan Mode 是内置的——用 /plan 或 Shift+Tab 循环模式切到它。它只读运行,先给一份你批准后才动手的 plan。Step 1 里的 prompt 是补充,逼这份 plan 以可独立成 PR 的形式返回。
agent PR 的行数上限取多少合理?
多数团队落在默认 200 行、硬顶 500 行(不含测试、lockfile、生成代码)。具体数字不如「把它写进 AGENTS.md」重要,这样 Codex 和 CI 才执行同一条线。
我明明在 AGENTS.md 里写了上限,Codex 却不理,为什么?
查三件事:文件在 Codex 会加载的路径上(repo 根目录、~/.codex、或当前工作子目录);它在 32 KiB 的 project_doc_max_bytes 上限以内、没被截断;上限放在清晰的 Constraints / PR sizing 标题下,而不是埋在大段散文里。CI gate(Step 5)能在模型跑偏时仍然把上限执行到位。
所有工作都在一个 squash 后的 commit 里,怎么拆 PR?
别 cherry-pick——没有可单独挑的颗粒度。按关切从 main 拉分支,用 git checkout main -- <paths>(或 git restore --source=<branch> --staged --worktree <paths>)只 stage 该关切的文件,commit,然后分别开 PR。
直接把上限调高让大 PR 过 CI 不行吗? 不行。上限存在是因为超过几百行后 review 质量会崩。要么拆开,要么对真正原子的改动走 feature flag(Step 6)分阶段发,让每个 PR 都保持可 review。