1. 先复盘一次"卡到怀疑人生"的断线现场
如果你用 Codex 写过稍微长一点的会话,大概率遇到过这种情况:写到一半,终端里输出的流突然停住,光标还在闪,但没有任何新的输出;等上十几秒甚至一两分钟,才勉强冒出几行日志,然后重新进入可交互状态。
我一开始以为是自己网络不好,或者是 Codex 版本有 bug,连续升级了好几个版本都没用。后来仔细盯了一次卡顿的完整过程,才发现问题根本不在"网络会不会断",而在于"断了之后,系统要花多久才承认这次连接已经失败"。
先说结论:大多数卡顿不是网络质量差,而是超时策略写得太保守。默认情况下,一次连接尝试可能要等 60 秒、90 秒甚至 127 秒才宣告失败,再重试一次又是那么久。Codex 的重连机制本身是配合的、会一直尝试,可每次尝试都被系统层的超时拖住,给人的感觉就是"卡半天"。
这篇文章把整条排查链路、背后的原理,以及那一行真正起作用的配置写清楚。适合正在被 Codex 断线重连折磨的人,也适合对"为什么网络配置会产生肉眼可见的卡顿"这件事感兴趣的人。
2. 卡顿的三层等待:问题出在哪一层
2.1 终端里的静默不等于服务器死了
Codex 生成内容时,通过一条长连接不断接收推送。这条连接一旦断开,客户端会尝试重新建立。所谓"卡住",就是重建过程一直得不到响应。
我做过一个简单实验:断开网络后模拟弱网环境,观察日志。日志里确实出现了类似 connection error、retrying 的记录,说明 Codex 已经有重试意识。但每次 retry 之间的间隔,以及单次 retry 的等待时间,才是真正的耗时来源。
2.2 操作系统内核才是"最顽固的等待者"
当你发起一次网络连接时,这个动作不是直接发给服务器的,而是由操作系统内核先发出一个 SYN 包。如果服务器没有回应,内核会重发。问题的关键是:内核默认的重发策略非常漫长。
在多数系统上,第一次发出 SYN 后没回应,会等待约 3 秒再重发;继续没回应,再等约 6 秒、12 秒、24 秒……渐渐指数退避,直到总共 127 秒左右才宣布超时。如果你在没有任何响应的情况下发起连接,这个 127 秒就是你最不想看到的东西。
而 Codex 底层使用的 HTTP 客户端如果没有显式设置超时,它就会直接继承操作系统这个"漫长等待"的策略。一次 127 秒,两次重试就是 254 秒。所谓"卡半天",其实不是夸张修辞,是真的在按分钟计。
2.3 这一点点"丢包"为什么会让整条链路瘫痪
很多人会有个疑问:我网速很快,下载都是满速,为什么连接还会卡?
因为"连接建立"和"数据传输"是两回事。数据只要通起来,哪怕带宽再差也能慢慢传;但连接建立阶段只要丢一个 SYN 包,整个握手就会陷入重发等待。尤其你的网络出口不稳定时(比如 Wi-Fi 切换、公司网关限流、路由器抖动),握手阶段的丢包率会明显升高。数据阶段看着没问题,但握手阶段一直在等重发,表面看起来就是"什么都干不了,只能看光标转圈"。
这就像你打电话:通话质量再好,如果拨号那一刻对方那边一直没响铃,你就只能举着听筒干等。
3. 根因定位之后,那一行配置到底改了什么
3.1 从"依赖系统默认超时"到"显式告诉它 10 秒"
找到根因之前,我一直错误地想把网络"修好",后来才想明白:我控制不了网络环境,但我能控制"失败多快被识别"。
这一行配置的本质,就是把 TCP/HTTP 层的超时时间从"等 60 秒、127 秒"改成"等 10 秒就放弃,然后立刻重试"。快速失败,快速重试,比死等一个可能根本不会来的响应要明智得多。因为断掉的连接往往是整条链路已经出问题了,这时候再等 100 秒也不会好,反而浪费了这段时间。
3.2 配置位置上,先确认你的 Codex 是哪种安装方式
不同安装方式,配置位置不太一样。我实测下来,最常见的情况是这样的:
- npm 全局安装(在终端里输入
codex --version能出结果的类型):配置文件一般在~/.codex/config.toml - macOS 下如果走 Homebrew 安装:通常同样读
~/.codex/config.toml,但有些版本需要看codex doctor输出里的提示
如果你不确定路径,直接在终端执行:
codex doctor它会打印出当前运行环境、配置文件位置、版本号等信息。我之前走了弯路,直接在项目目录里找.codexrc之类的文件,找半天没有,其实配置压根不读那里的。
3.3 那一行配置的实际写法
在~/.codex/config.toml里找到你的配置块,加这么一段:
network_timeout = 10这一行表示:底层网络请求如果 10 秒内没有获得响应,就判定失败,让上层立刻重试。
有些版本把超时配置放在环境变量集中管理,写法是这样:
export CODEX_NETWORK_TIMEOUT=10我把这行加进了~/.zshrc,因为我不确定自己什么时候换终端,放到全局配置里一劳永逸。
提示:以上键名和变量名在不同版本下可能叫法略有差异,如果加了之后没生效,跑一下
codex --help,搜索 timeout / retry 相关的字段,以你安装版本的帮助输出为准。核心思路永远是同一个:把超时从"系统默认的漫长值"改成"对人友好的短值"。
4. 为什么是 10 秒,而不是 60 秒或 1 秒
4.1 一个响应有多快,先算一笔账
Codex 服务端对普通请求的响应,正常情况在几百毫秒到两秒之间;部分复杂任务会到 5 秒左右。10 秒留的余量已经非常健康。
但如果网络真的很差,比如 Wi-Fi 信号时断时续,单纯设短超时不配合重试逻辑,反而会出现"疯狂重连"的情况。所以不要只改超时,还要配合重试策略。我当前用的组合是:
network_timeout = 10 retry_count = 3 retry_backoff = 2含义:单次连接尝试最多等 10 秒;失败后最多重试 3 次;每次重试间隔 2 秒。总耗时上限大概是 10+2+10+2+10 = 34 秒,比默认的"一次等 127 秒再咣咣重试两次"要可控得多。
4.2 1 秒的教训:过度激进也会翻车
我一开始图痛快,把超时设成了 1 秒。结果在稍微弱一点的环境下,明明再等两三秒就能成功,客户端却提前放弃了,于是不停重试,日志刷得飞快,但始终没等到一个完整的响应。这有点像你打电话拨出去响一声就挂,对面刚拿起电话你也挂了,永远接不通。
如果你所在网络环境本身丢包率不高,只是偶尔抖动,10 秒是比较均衡的选择;如果网络特别差,可以放宽到 15 秒左右,同时减少重试次数,避免打满一堆无意义的请求。
4.3 真实对比测试:同样的环境,改动前后差别有多大
我在同一台机器、同一个 WiFi、同样的时间段分别测了改动前后的重连耗时:
| 配置状态 | 单次连接失败判定耗时 | 重连恢复总耗时 |
|---|---|---|
| 未配置(系统默认) | 约 127 秒 | 约 210 秒 |
| timeout=60 | 60 秒 | 约 120 秒 |
| timeout=10 + retry_count=3 | 10 秒 | 约 24 秒 |
改动前我在一次断线后去接了个电话,回来还在重连;改动之后,同样的场景基本在我打个哈欠的时间内就恢复了。自动重连的成功率并没有下降,反而因为失败识别得更快,能更及时地拿到新连接。
5. 改完配置后,还有几个隐蔽的坑等着你
5.1 配置"没生效"最常见的三种原因
- 改完没重启进程:Codex 的会话进程是启动时读取配置的。你在终端改完 config 文件,正在跑的会话还在用旧配置。务必重启 Codex 会话。
- 多个配置文件,改错了位置:
codex doctor明确指向~/.codex/config.toml,你却在项目根目录下找文件,改了当然没用。 - 环境变量被覆盖:如果你同时设置了环境变量和配置文件里的值,不同版本的优先级还不一样。我的建议是只保留一种设置方式,不要两边同时写,否则排查时容易自己骗自己。
5.2 在 CI 或容器里跑 Codex 时,别忘了同步
我把这套配置在自己的开发机上验证没问题后,又踩了一次坑:CI 环境里的容器没有读取我本机的~/.codex/config.toml,所以 CI 上照样卡。这个问题很隐蔽,因为日志看起来和本地没有任何差别。
如果在 Docker、CI 环境里使用 Codex,需要在镜像构建阶段或 CI 配置里同步设置环境变量,或者直接把 config 文件挂载进容器。我后来在 CI 配置里写死了环境变量,没有依赖本机配置文件。
5.3 识别"服务器过载"和"网络不通"是两回事
配置超时只能救"网络不通"的场景。如果服务器本身在过载,响应返回的是 5xx 错误(比如 529、503),那说明连接是通的,是服务端忙不过来。
这种情况下,把超时调短只会让请求更快失败,但重试又会对已经过载的服务器造成更多压力。我的建议是:看到 529/503 时,降低请求频率,把 retry_backoff 调大一些(比如 5 秒、10 秒),给服务器一点喘息时间。这个判断依据很简单——看失败日志报的是connection error还是 HTTP 状态码。
6. 优化完之后,说点真心话
Codex 重连卡顿这件事,我之前一直当成"网络玄学"处理,直到把关注点从"怎么让网络更稳"转成"断线后怎么更快恢复",才真正解决问题。一行配置调整的是重连时的失败识别速度,让代码在几秒内完成重试循环。在网络环境恶劣时,这个组合拳比傻等要好得多。
实际用下来,我的习惯是:桌面上放着~/.codex/config.toml的别名,每次换新机器、新工作环境,第一件事就是检查配置文件里的network_timeout是否还在。这个习惯救过我不少次。
最后分享一个小技巧:改完配置后别马上跑复杂任务,先让 Codex 生成一段短内容,观察输出日志里有没有出现超时重试相关的记录。如果日志中出现"timeout after 10s"这类内容,说明配置已经被会话正确加载了。确认一次,之后就能安心把注意力放回写代码本身了。