让 Codex 尽可能重试:stream_max_retries 与无限重连配置
最近把 Codex 的 stream_max_retries 调到 10 后,我发现界面经常停在:
1 | Reconnecting... 10/10 |
看起来像是第 10 次重试后卡死了。翻了当前 Codex 源码后,发现这里有两个容易误解的机制。
本文基于 2026-08-19 的
openai/codexmain 分支(commitf5a3dc5)记录,后续实现可能变化。
1. stream_max_retries 不限制所有重试
stream_max_retries 默认是 5,配置硬上限是 100。它控制的是普通 streaming response 断开后的重连次数。
但 Codex 现在还有一个默认开启的 feature:
1 | [features] |
当错误被分类为 ConnectionFailed 时,sampling 请求会绕过 stream_max_retries,进入另一套无限重试逻辑:
1 | 5s -> 10s -> 20s -> 40s -> 60s -> 60s -> 60s -> ... |
也就是说,stream_max_retries = 10 并不意味着“整个请求最多重试 10 次”。
相关源码:
2. 10/10 本身也可能要等很久
普通 stream retry 使用的退避大致是:
1 | 200ms * 2^(attempt - 1) * jitter |
所以设置 stream_max_retries = 10 后,各次等待约为:
1 | 0.2, 0.4, 0.8, 1.6, 3.2, 6.4, 12.8, 25.6, 51.2, 102.4 秒 |
第 10 次单次等待就约 102 秒,前 10 次累计约 205 秒。因此看到 10/10 时,程序很可能并没有死,只是在执行最后一次很长的 exponential backoff。
源码中的这套 backoff 没有为普通 stream retry 设置 60 秒上限:
此外,默认 stream_idle_timeout_ms 是 300000,即 5 分钟。一次重试真正发出去后,如果连接没有事件,又可能继续等很久。
3. WebSocket fallback 还会重置计数
当普通 stream retry 用尽后,如果当前走 WebSocket,Codex 还会尝试 fallback 到 HTTPS,并把 stream retry 计数清零。
所以实际流程可能是:
1 | WebSocket 重试 N 次 |
这进一步说明 stream_max_retries 更接近“当前 transport 的 stream retry budget”,而不是整个 turn 的最大尝试次数。
4. 目前 config 能做到什么
如果目标是网络断掉后尽量一直等到恢复,可以保留:
1 | [features] |
如果不希望 HTTP 内层再做一轮指数退避,可以把 provider 的:
1 | request_max_retries = 0 |
同时不要把 stream_max_retries 设得太大。我现在更倾向于类似:
1 | [features] |
不过要注意:仅靠 config.toml 目前无法实现“所有 retryable error 都无限重试,并且固定间隔、不做指数退避”。ConnectionFailed 的无限重试仍然会从 5 秒退避到 60 秒;其他 stream error 仍然使用有限次数的指数退避。要得到完全固定间隔的无限重试,只能修改 Codex 的 retry 实现。
结论
最需要记住的两点:
stream_max_retries不是整个 Codex turn 的最大重试次数;ConnectionFailed默认可以无限重试。- 把它从
5调到10的代价远比“多重试 5 次”大,因为最后一次 backoff 已经增长到约 102 秒。
所以遇到 Reconnecting... 10/10 长时间不动时,先别急着判断死锁:它很可能只是 Codex 当前 retry 策略的正常结果。