Codex 返回一份 patch,宣布「I have completed the changes」,结果你发现:一半 call site 还在用旧签名、两个 import 指向已经被删掉的函数、类型能编译但有一个 test 永远跑不完。Patch 没收完——是 Codex 自己宣布收完了。
最快的修法: 在 prompt 里(更好的做法是写进 AGENTS.md)加一个 DONE means 块,列出 Codex 必须运行并贴出输出的 verifier 命令,然后你自己再把同样的命令跑一遍,只信 exit code、不信对话框里的话。模板见下面 Step 1。
这是一个*「完成判据」失效*问题,不是「智商」问题。Codex 在它自己判断工作完成时停手;你不把「完成」写成可度量的标准,它就停在它的判断恰好落到的那个点上。(注意:这跟 apply_patch 工具报错是两回事,比如批准后出现 Failed to apply patch 或 No such file or directory——那些是机械失败,编辑根本没落地。看到那些字符串说明 patch 不是收了一半,而是根本没应用,修法是升级 Codex CLI 或重跑这次编辑,不是收紧 prompt。)
截至 2026 年 6 月,Codex CLI 默认用 GPT-5.5(用 /model 切换;picker 里还有 GPT-5.4 和更轻量的 Codex-Spark)。GPT-5.5 比早期模型更细致,但只要你不给它一个「完成」的定义,它照样会停在它自己那个完成判断上。
你属于哪一类?
重新 prompt 之前先跑一次 grep、一次 verifier——症状会告诉你病因。
| 你观察到的症状 | 最可能的病因 | 去 |
|---|---|---|
| Prompt 里从没出现「done」/「complete」/某个 verifier 名 | 任务没定义 done | Step 1 |
| 前面的文件改了,后面的文件(第 6 个起)漏了 | 中途上下文衰减 | Step 4 |
| Codex 说「过了」但你本地 typecheck exit 非 0 | 输出截断遮住了失败 | Step 3、Step 6 |
| 最后的推理短得可疑,末尾约 20% 缺失 | 撞到思考/轮次预算 | Step 4 |
| grep 旧符号仍有匹配 | Codex 在某个模糊 call site 上静默跳过 | Step 2 |
| 一条 bullet 做得很好,其他只做了 30% | Prompt 把多件事打包了 | Step 4 |
常见原因
按命中率从高到低排。
1. Task 根本没定义 done
「把 getUserById 改成查不到时返回 null」——done 是什么意思?能编译?通过现有测试?补一个新测试?所有调用方都改了?没说的话,Codex 默认按最浅那条理解。
如何判断:回看你的 prompt。如果里面没出现「done」「complete」或某个可度量的 verifier(typecheck / tests / lint),那就是 Codex 自己挑了个定义。
2. Codex 在 patch 中途用光了有效上下文
跨多个文件的改动里,前 3 个文件 Codex 还记得清清楚楚,但到第 6 个文件时,它已经在靠一份正在褪色的第 1 个文件的记忆工作了。签名改动在 service.ts 里改对了,但 controller.ts:142 漏了——因为 Codex 已经记不清 service.ts 长什么样了。跑 /status 看这个 session 烧掉了多少上下文窗口——快满时,这就是你的病因。
如何判断:数「应该改的文件数」+「实际改了的文件数」。如果「应该改」大于「实际改」,而漏掉的文件是 session 早期 grep 列出来的,那就是上下文衰减。
3. 工具(test / typecheck)静默失败、Codex 当成了成功
pnpm typecheck 2>&1 | tail -10 返回了 10 行看着挺干净的内容,但真正的报错在 200 行之前。Codex 只看到 tail、判定干净、就停手了。
如何判断:本地用同一个 verifier 跑出全部输出。如果本地 exit code 非 0、Codex 却以为通过了,就是输出截断遮住了失败。
4. Codex 撞到思考/轮次预算、提前急刹
Codex 限制每一轮能做多少工作。预算见底时,它会提前下结论「差不多了」然后一交就走。
如何判断:对话里 Codex 最后一轮的推理比前几轮明显短。或者 patch 覆盖了 diff 的 80%,最后 20% 缺失——这种「时间不够用了」的形状。
5. Codex 在一个模糊的 call site 上对冲了
patch 应该把 getUser 全局改名成 getUserById。在 tests/fixtures/old-data.ts 里,文件把 getUser 当一个字符串从 JSON 快照里读出来。Codex 不知道该不该动它,跳过了,也没标记这个跳过。
如何判断:patch 落地后 grep 一下旧名字。如果还有非零匹配、而 patch 里也没提到它们,就是 Codex 静默跳过了。
6. 任务本质上把多个关切打包了
「重构 auth、加 password reset、再更新文档」——三个任务塞在一个 prompt 里。Codex 把第一个做完,第二个做一半,第三个忘了,然后说「done」。80/20 是落在每个子任务内部的,不是横跨它们之间。
如何判断:把做完的内容跟你 prompt 里的 bullet 列表对照。如果一条 bullet 得到了全部注意力、其他只得到 30%,那这个任务本该拆成三次跑。
最短修复路径
按 ROI 从高到低排。光是 Step 1 就能修掉大多数没收完的情况。
Step 1:在 prompt 里把 done 绑给 verifier
用这个模板:
任务:[一句话目标]
DONE 的定义(以下全部通过、且零新增 error):
1. `pnpm typecheck` — 报告 exit code + 最后 20 行
2. `pnpm test -- --reporter=verbose` — 报告 pass/fail 计数
3. `pnpm lint --max-warnings 0` — 报告 exit code
说「done」之前,把每条命令的输出贴出来。
任何一条 fail,就不要说 done——先修、再重跑。
「把输出贴出来」这一条强制 Codex 真的去跑命令,而不是脑补它们通过了。
让它持久化——把 verifier 写进 AGENTS.md。 按 OpenAI 的 AGENTS.md 指南,AGENTS.md 就是 Codex 读取项目指令的地方,而它推荐的模式恰好就是这类规则(「Always run npm test after modifying JavaScript files」)。写一次这个块,就不用每次 prompt 都重打一遍:
## Definition of done
A change is only complete when all of these pass with zero new errors.
Run each, then paste exit code + last 20 lines before claiming done:
- `pnpm typecheck`
- `pnpm test -- --reporter=verbose`
- `pnpm lint --max-warnings 0`
Never say "done" while any verifier is non-zero.
Codex 读取 AGENTS.md 的顺序是:先读 ~/.codex/AGENTS.md(全局),再从 git 根目录一路读到你当前目录的每一个 AGENTS.md,越靠近当前目录的文件优先级越高(会覆盖前面的)。超过 project_doc_max_bytes(默认 32 KiB)的文件会被静默截断、空文件会被跳过,所以这个块要短。用 /status 确认 Codex 确实把它加载进来了。
Step 2:完成前要求一份「文件覆盖报告」
对多文件改动,加这段:
说 done 之前:
1. 列出本任务需要改动的每一个文件。
2. 每个文件给一行 diff 摘要。
3. 确认没漏掉被改函数的任何调用方:
grep -rn "oldFunctionName" --include="*.ts" --include="*.tsx" .
结果 > 0 就是漏了 call site——去修。
Step 3:从外部验证,别信它的汇报
Codex 返回后,你自己把同一个 verifier 跑一遍:
pnpm typecheck && pnpm test && pnpm lint
echo "Exit code: $?"
如果 exit code 非 0,那 patch 就是没收完,不管 Codex 怎么说。你也可以在 Codex 里跑 /diff 看完整 git diff(含未跟踪文件),跑 /review 让一个专门的 reviewer 读工作树、把漏掉的 call site 标出来而不改任何代码。然后把真实错误贴回给 Codex:
Verifier 没过,输出:
[贴]
你说 done 了,但 typecheck 报了上面这些。修,然后重跑。
三个 verifier 全部 exit 0 之前,不要说 done。
Step 4:按自然的关切边界拆任务
如果任务碰了三个关切,或者 /status 显示上下文窗口快满了,就拆成独立的 session 跑:
Session 1:把 getUser 全局改名成 getUserById。停。
Session 2:在新签名里加上缺失的 null 检查。停。
Session 3:补上覆盖 null 返回的测试。停。
每个 session 都有一个能装进上下文的小「done」定义。用 /new 开新 session,免得上一个任务里那份正在褪色的记忆渗进来。
Step 5:patch 没收完时,要一份「收尾 diff」,别要重写
如果 Codex 停在 80% 完成,别重跑整个任务。把它钉在剩下的工作上:
你已经在 service.ts 里改完了 patch,但下面这些 call site 还在用旧签名:
- controller.ts:42
- routes/api.ts:118
- tests/fixtures.ts:7
只改这三个位置。改完跑 typecheck。报告 exit code。
Step 6:给 verifier 放宽输出预算
如果 Codex 因为输出被截断而漏看了 error,就去掉 | tail -50:
# 差——遮住了上游的错误
pnpm typecheck 2>&1 | tail -50
# 好——全输出,并把出错文件高亮出来
pnpm typecheck 2>&1 | grep -E "error TS|^[a-z].*\.tsx?:" | head -100
按 error 模式过滤,而不是按一个随意的行数截尾;Codex 看到的就是真正的错误,而不是末尾那行总结。
怎么确认真修好了
只有以下三条全部成立,patch 才算真正收完:
pnpm typecheck && pnpm test && pnpm lint在你自己的终端里 exit0(不是只在 Codex 的汇报里)。grep -rn "oldSymbol" --include="*.ts" --include="*.tsx" .返回零个你并不打算保留的匹配。/diff(或git diff --stat)列出了你预期的每一个文件——一个都不少。
任何一条不成立,你就还在上面某个分类里;回到对应的那一步。CI 在 PR 上重跑这同一组命令,是最后一道闸。
预防建议
- 在
AGENTS.md里放一个「Definition of done」块,列出 verifier 和要贴的输出,这样每个 session 都自动继承它。 - 永远从外部验证——agent 的「done」汇报永远不是真相,exit code 才是。
- 按关切边界拆 prompt:一个任务对应一个 done 判据对应一个 session。
- 多文件改动,要求 grep 一遍旧符号,作为「没漏 call site」的证据。
- 盯着
/status看上下文压力;在它填满之前就开新 session。 - Codex 停在 80% 时,把它明确钉在剩余文件上,而不是重跑整个任务。
- 把 CI 接成最后一道闸——即便是已经验证过的 Codex patch,merge 前也再过一遍。
FAQ
为什么 patch 明明没收完,Codex 还说「done」? 因为「done」是它自己的判断,除非你定义了它。没有可度量的判据,Codex 就停在那个看起来还算合理的、最浅的理解上。给它一份 verifier 列表(Step 1),这个判断就从主观变成了一个 pass/fail 检查。
把 verifier 写进 AGENTS.md,真能让 Codex 去跑它们吗?
能让 Codex 大幅更可能去跑、也能减少每次 prompt 都要叮嘱的麻烦,但这是指引、不是硬性闸门——OpenAI 的文档把 AGENTS.md 描述成指令,不是强制。永远自己再跑一遍 verifier(Step 3);exit code 是唯一一道没法被「说服」绕过去的闸。
怎么区分上下文衰减和思考预算截断?
跑 /status。如果上下文窗口快满了,那是衰减(Step 4——拆任务)。如果上下文还宽裕、但最后一轮推理异常短、末尾约 20% 缺失,那是轮次预算(也走 Step 4——但 Step 5 那种定向收尾 prompt 通常一轮就能补完)。
apply_patch 报了个错——这是同一个问题吗?
不是。像 Failed to apply patch、command failed; retry without sandbox? 或批准后的 No such file or directory 这类字符串,是机械性的工具失败:编辑根本没落地。它们集中在近期 Codex CLI 构建里的一个 sandbox 回归上(大约 0.115.0–0.134.0 都有报告,0.118.0 是一个明显的断点,而 0.117.0 没问题),bubblewrap sandbox 挡住了写入,Codex 就卡在那个 retry 提示上反复打转。修法是用 codex --version 查版本并升级到最新,或者临时把 session 切到 --full-auto / danger-full-access 让写入不走 sandbox——不是收紧你的 prompt。
我只想让它把剩下的收完,Codex 却老是重做整个任务。 别再让它「finish the task」,而是点名剩余的确切位置(Step 5)。一个限定范围的「只更新这三个 call site」的 prompt 能让 diff 保持小、让改动装得进上下文。