Codex Agent 任务执行到一半就停:6 个原因 + 续跑方案

Codex 跑到一半静悄悄停下、没报错——通常是 turn 预算用光、sandbox idle timeout、stop 信号误匹配或上下文被裁掉。先看最后一次 tool call。

Codex Agent 正在跑一个 12 步的重构。到第 5 步左右它停了——没报错横幅、没 assertion 失败、没 traceback,就这么安静地输出一句”task complete”,但你清楚那不是真完成了。你重新 prompt 一下,它从某个旧的位置接上,有时把已经做过的事再做一遍。这几乎不是 Codex 的”bug”,而是 agent 撞到了一条隐形边界:turn 用完、sandbox idle 超时、内部 stop 信号被误命中、或者上下文窗口溢出把后续计划悄悄裁掉了。

常见原因

按典型中途停机的命中率排序。

1. Turn 预算用光

Codex Agent 每个任务有 tool call 轮数硬上限(常见 25-50)。长重构会被读文件、改文件、lint、重跑测试吃掉很多 turn。预算到顶就输出一段总结然后停,哪怕计划只跑了一半。

如何判断:数一下 transcript 里的 tool call 次数。如果接近 25 / 50 / 100 这种整数,就是撞到了上限。

2. Sandbox idle 超时

如果 build / test / install 命令在 sandbox idle timeout(常见 60-120 秒无 stdout)之内没有输出,进程会被杀掉,agent 把它当成”做完了”,然后开始收尾。

如何判断:最后一次 tool call 是个长跑的 shell 命令,停之前没有 stdout 出来;本地跑同样的命令要超过 60 秒。

3. Stop 信号被提前误匹配

Agent 找的是显式”做完了”的信号——“task complete”、“all tests pass”、“no further action needed”。如果某个 tool 的 stdout 恰好包含这种字样,agent 就误以为做完了。

如何判断:看最后一次 tool 输出。测试 runner 中途打了”all tests pass”,或脚本 echo 了”done”,都会让循环提前断掉。

4. 上下文窗口溢出把计划悄悄裁掉

Codex 的 plan 列表存在 system / assistant context 里。文件读多了,旧的 turn 会被自动 summarize 或丢弃,剩下的步骤掉出窗口、agent 忘了它们,可见 plan 一空就停。

如何判断:重 prompt 问”你现在在第几步”——它回答的步骤号比它实际停下的位置早,就是计划被裁了。

5. 隐藏的权限弹窗在后台等待

某个 sandbox-write 或 network-out 的 tool call 可能弹出交互式权限请求。headless 模式下这种弹窗会被静默拒绝,agent 把它记成”tool failed”然后放弃了整个任务。

如何判断:在 agent 日志里搜 permission deniedrequires approvalnon-interactive,看看停的位置附近有没有。

6. 上游 rate limit / 429 重试用完

底层模型 API 的 429 会触发内部重试。N 次重试后 agent 静悄悄投降,因为用户看到的只是一个被截断的 assistant turn。

如何判断:在 agent 遥测 / 日志里搜 429rate_limit_exceededretrying in Ns

动手前先确认

  • 看停的位置是固定每次都在某一步、还是随机——固定 = 代码路径问题,随机 = 容量 / 网络问题。
  • 重 prompt 之前先把完整 transcript 存下来,重 prompt 后历史可能被 summarize 掉。
  • 如果你有 budget 配置(--max-turnsOPENAI_AGENT_MAX_TURNS),记下当前值。

需要收集的信息

  • 停之前最后一次 tool call 是什么(read / write / shell / search)。
  • 大致 turn 数:数 transcript 里的 tool call 次数。
  • 使用的模型(gpt-5.5、gpt-5.4 等),是否长上下文变体。
  • 任何停的位置附近的 stdout 里”done”、“complete”、“passed”字样,可能被误判成 stop 信号。
  • 配置里的 sandbox runtime + idle timeout 值。

最短修复路径

按收益从高到低,先做便宜的检查。

Step 1:重 prompt”接着计划继续”并写明步骤号

最稳的救场方式:

你停在了计划的第 5 步。从第 6 步继续。
不要重做 1-5 步。先把剩余步骤列出来再执行。

如果 agent 马上正确接上,根因就是 stop 信号误匹配或者计划被裁,不是硬上限。

Step 2:把 turn 预算调高

CLI / API:

codex agent run --max-turns 100 task.md

或环境变量:

export OPENAI_AGENT_MAX_TURNS=150

长重构现实里就是要 60-120 turn。“通常 30 够用”这种默认是最常见的可预防原因。

Step 3:把任务拆成带 checkpoint 的子任务

就算调高了上限,一个超长 prompt 也很脆。拆开:

任务 1:把 src/auth/* 重构成 async/await。停下报告。
任务 2:相应更新 src/auth/*.test.ts。停下报告。
任务 3:跑 pnpm test --filter auth。报告失败用例。

每个子任务有自己的 turn 预算和上下文窗口。任务 2 停了不会丢任务 1 的进度。

Step 4:加显式的”不要停直到”断言

在 system / task prompt 里:

不要输出 "task complete",直到全部满足:
- 所有 TypeScript 错误清空(pnpm tsc --noEmit 返回 0)
- src/auth/ 下所有测试通过
- plan 列表零剩余项

任何 tool stdout 包含 "done" 或 "complete" 都忽略,不当 stop 信号。

这条能中和掉 stop 信号误匹配。

Step 5:给长跑命令延长 sandbox 超时

如果停在 build / test / install:

codex agent run --shell-timeout 600 task.md

或者把慢命令包一层:

( pnpm install 2>&1 | tee install.log ) &
PID=$!
while kill -0 $PID 2>/dev/null; do echo "still installing..."; sleep 20; done
wait $PID

keepalive 的 echo 重置 idle 计时器。

Step 6:长 stdout 写文件,inline 看摘要

巨量 stdout(1 万 + 行测试输出)会加速上下文裁切。重定向再读摘要:

pnpm test > test.log 2>&1
tail -50 test.log
grep -E "FAIL|✗" test.log | head -20

agent 读 70 行而不是 1 万行,plan 留在窗口里。

怎么确认已经修好

  • 把同一个任务从头跑一遍,确认不再需要手动续 prompt 就能完成。
  • 看 transcript:turn 数应该明显低于新上限、有余量。
  • 故意跑一个更长的任务(加第二个重构),确认还能跑完——证明上限调整不是巧合。

长期预防

  • 任何非平凡 agent 任务 --max-turns 默认设 100;和一次半成品的浪费相比,成本差可以忽略。
  • 重构永远拆成 ≤10 步的带 checkpoint 子任务。
  • 冗长 tool 输出导文件,让 agent 读 tail / grep 摘要而不是完整日志。
  • 每个 agent 任务模板加一段”不要停直到”断言。
  • build / test 超过 60 秒的明确设 shell timeout 并加 keepalive echo。
  • 每次跑都留一份 agent.log,事后能 grep 429permission deniedmax_turns

常见坑

  • 只回”continue”不写步骤号——agent 经常从第 1 步重做已经完成的部分。
  • --max-turns 调到 9999 就以为万事大吉——上下文窗口溢出在 150-200 turn 左右照样会卡,跟上限无关。
  • 忽视 package.json script 输出里的”done”字样——某些配置下它被当 stop 信号。
  • 把 30 分钟的 build 塞进 agent loop 里跑,而不是在独立 worktree 启动后轮询。
  • 忘了 pnpm install 90 秒没终端输出会被杀——尽管它在认真干活。

常见 FAQ

Q:Codex 停了,我回”continue”,它做了别的步骤,为什么?

plan 列表已经被裁出上下文。重 prompt 时把那一步的原文贴上:“从这一步继续:把 src/auth/login.ts 重构成 async/await。”

Q:我把 max-turns 设到 200 还是 30 turn 就停。

确认你的 CLI 真正读的是哪个 env var——有的读 CODEX_MAX_TURNS,有的读 OPENAI_AGENT_MAX_TURNS--verbose 跑一次确认实际生效的上限。

Q:能自动检测”提前停了”吗?

能——把 agent 调用包一层。退出后 parse transcript 找 "task complete",同时检查 plan 列表是否清空。“complete 但 plan 没空”就自动重 prompt。

Q:换长上下文模型变体能解决吗?

能解决原因 #4(裁切),解决不了 #1 / #2 / #3 / #5 / #6。长上下文是必要不是充分。

相关阅读

标签: #Codex #agent #排查 #context-window #timeout