如果你最近在用 Codex CLI 干活,大概率撞见过这个画面:网络稍微抖一下,终端就卡在Reconnecting to service...的状态里,光标一直转,敲什么都没反应;运气差点的,重启两三次还是卡。我一开始也以为是密钥过期、会话文件坏了,折腾了两三天,最后才发现根子压根不在密钥上——是客户端那个近乎苛刻的请求超时和重试策略在互相打架,把一个正常的"慢响应"放大成了"长时间假死"。今天这篇不聊概念,就写我怎么从日志里把这条路径摸出来,以及最终落在config.toml里的那一行配置。
这篇东西适合所有用 Codex 做日常开发、尤其习惯在远程开发机或长任务场景下跑 Codex 的人。你不需要懂 Rust,也不需要会改源码,只要会看日志、会编辑一个配置文件,五分钟后就能自己复现一遍完整的排查过程。
1. 先搞清楚"重连卡半天"到底卡在哪
1.1 复现症状:卡住的时候,网络其实没有断
我最早遇到这个问题的场景很典型:办公网在下午高峰期偶尔抖动,无线信号也不稳定,Codex 在处理一个比较大的重构任务,跑到一半终端突然不再输出新内容,左下角出现Reconnecting之类的状态提示。第一次遇到时我以为只是网断了,等它自己恢复就好,结果等了十来分钟还是那个转圈状态。
后来我换了有线网络、重启了 Codex、重新登录了好几次,问题照样出现。最诡异的一点是:系统网络明明正常,浏览器开网页、拉代码都没问题,唯独 Codex 一直在"重连"。这时候我才意识到,它卡住不是因为物理断网,而是客户端自己进入了一个反复重试的循环。这个循环一旦启动,界面只显示一个"重连中",没有进度、没有日志、没有超时倒计时,用户能做的只有干等。
注意它的触发时机:通常不是刚开始对话就卡,而是发生在某个请求已经发出、且长时间没有返回的时候。换句话说,Codex 不是在"连不上",而是在"等一个响应但迟迟等不到"。
1.2 原因一:请求超时太短,重试退避变成了无限循环
Codex CLI 调用模型服务走的是 HTTP 请求,而大模型推理本身就不是一个"秒回"的操作。你把一段很长的上下文丢给它,它需要先消化所有这些内容,再开始生成,这个阶段完全没有任何输出返回。第一次 token 返回之前,客户端只能干等。
问题在于,客户端对"等多久算超时"的判断非常保守。我这边实际观察到的场景是:当某个请求的处理时间超过客户端内部设定的等待窗口,它就会判定这次请求失败,然后自动进入重连流程——不是直接报错让你看到,而是先等一个退避间隔,再重新发起连接。如果第二次还是超时,退避间隔会翻倍,1 秒、2 秒、4 秒、8 秒……连试几轮之后,单次等待就已经到了分钟级。
你从用户界面看,就是那个转圈状态一直转;但从客户端内部看,它其实是在一次次休眠和重试之间来回切换。生活里找个类比的话,就像你在餐厅等位,服务员告诉你"还有 5 分钟",结果每 5 分钟她又说一次"还有 5 分钟",你走也不是、留也不是,坐那儿干耗着。
1.3 原因二:认证会话过期后,刷新流程在非交互环境下卡死
第二种常见原因和网络无关,纯粹是认证问题。Codex 的登录态保存在~/.codex/auth.json这类文件里,access token 是有有效期的,过期之后客户端会尝试用 refresh token 去换新的。换 token 的过程中如果服务端响应慢,或者当前环境不能弹出交互式确认(比如你是在 SSH 会话里跑的 Codex),客户端就会卡在等待登录确认的状态上。
这种卡死有个特征:你切到别的终端看进程,Codex 还活着,CPU 占用也不高,但它就是不再处理任何请求。检查认证文件也看不出明显问题,因为文件存在、权限也正常,纯粹是刷新流程没有一个可行的出口。
提示:遇到重连卡顿,先判断是"请求超时"还是"认证过期"。最简单的区分方式就是看日志里重试的间隔——如果是 1 秒、2 秒、4 秒这样翻倍增长,基本是请求超时;如果是反复出现"refreshing token"之类字样但没有任何进展,基本是认证刷新流程被卡住。
2. 一行配置的落点:看懂 Codex 的配置文件
2.1 配置入口与基本结构
Codex CLI 的配置入口是用户目录下的~/.codex/config.toml,如果你设置过CODEX_HOME环境变量,那么路径会跟着变。这个配置文件是 TOML 格式,和 Python 的pyproject.toml、Rust 的Cargo.toml是一路货色,结构上就是区块加键值对。
一个最基本的配置文件长这样:
model = "gpt-4.1" # 指定默认模型 model_provider = "openai" # 指定默认供应商 approval_policy = "onRequest" # 权限审批策略model决定默认用哪个模型;model_provider指定走哪套接口配置;approval_policy控制 Codex 在执行命令前需不需要征求你同意。日常用的话,前三行一般不用动,真正容易出问题的反而是后面那些关于超时、重试、会话恢复的细节配置。
在动手改之前,我强烈建议先把原始文件备份一份:
cp ~/.codex/config.toml ~/.codex/config.toml.bak这样后面不管怎么折腾,一条命令就能还原,不至于改坏了之后连 CLI 都起不来。
2.2 最关键的一行:把请求超时从"秒级"调到"分钟级"
我最终解决问题用的配置,就是在配置文件里给当前使用的模型供应商增加一个请求超时设置。不同小版本的 Codex 对超时配置的命名不完全一致,我这边验证过的写法是在[model_providers.xxx]段里加一行:
[model_providers.openai] timeout_ms = 600000600000的单位是毫秒,换算过来就是 10 分钟。为什么选 10 分钟?先看一个典型的大请求耗时:假设上下文里有几万 token,模型需要读完这些内容再开始推理,普遍需要 30 秒到 2 分钟才能返回第一个 token;如果后面再叠加工具调用、多轮生成,整个请求走完 3 到 5 分钟非常正常。
Codex 客户端默认的等待窗口我实测下来偏小,经常在 60 秒上下就触发超时判定。你想想,一个明明需要 3 分钟的请求,客户端 60 秒就判定它失败了,然后开始退避重试,这不光解决不了问题,反而把一次正常请求拆成了好几轮"失败—等待—重试"的死循环。把超时调到 10 分钟,等于告诉客户端:别急,给它点时间,它不是在装死,是真的在算。
注意:这个字段名在不同版本里可能有差异。如果你的 Codex 启动时提示
unknown field,说明当前版本不认这个写法,这时候先看看codex --version的输出,再根据错误提示去掉或调整字段名。配置文件里少一行配置最多是不生效,但多一个版本不认识的字段可能会导致整个配置文件加载失败。
2.3 配套项:别让重试变成雪上加霜
光调大超时还不够,我顺手把重试次数也调低了。原因很简单:退避重试机制本身是好东西,但它在"大模型请求本来就慢"的场景下帮不上忙,反而会把问题放大。本来只需要等一次 3 分钟的请求,结果因为超时判得太早,变成"等 60 秒失败、等 2 秒重试、再等 60 秒失败、等 4 秒重试……"这种无限循环。
我当时在配置顶层加了一行:
request_max_retries = 1意思是请求失败后最多再重试一次,不要陷入不断翻倍等待的深渊。这一项配合 10 分钟超时,效果立竿见影:大多数情况下第一次请求就能正常走完,根本轮不到重试;万一真的失败,也能快速报错,让我知道是模型服务端的问题,而不是在终端前干等。
还有一个可选项是启动超时,如果你发现 Codex 启动阶段也经常卡很久,可以在配置里加上startup_timeout = 300(单位是秒),限制启动阶段的等待时间。这个字段不是每个版本都有,没有的话直接忽略就行,不影响其他配置生效。
2.4 不想改文件?环境变量一样能解决问题
如果你不想动配置文件,或者你是在 CI/CD 这种临时环境里跑 Codex,环境变量是更轻量的选择。把 API 密钥通过环境变量注入,避免走交互式登录流程,这类问题也能绕开一大部分。
比如配置文件里对应供应商的密钥字段用的是env_key,那你可以这样指定:
export MY_PROVIDER_API_KEY="你的密钥"这样 Codex 启动时会直接从这个环境变量读取密钥,不会去走 token 刷新那套流程。至于超时相关的环境变量,不同构建的 Codex 命名不统一,我建议先用codex --help看一眼当前版本支持哪些变量。如果你不想折腾,直接用第一节说的配置文件方案就行,它适用于绝大多数场景。
3. 实操记录:从卡死到恢复的三步排查
3.1 第一步:打开日志,定位触发点
很多人在遇到重连卡顿的时候习惯直接去翻配置文件,或者干脆重装,但我建议先开日志。Codex 底层是 Rust 写的,支持通过RUST_LOG环境变量控制日志输出级别。启动命令就是:
RUST_LOG=codex=trace codex然后复现一次刚才卡住的场景,切到另一个终端看日志输出。重点观察两个地方:一是是否存在retrying或者backoff之类的关键字;二是每次重试之间的时间间隔。如果间隔呈现出 1 秒、2 秒、4 秒这种翻倍规律,那就实锤了——客户端一直在退避重试,而不是在真正尝试连网。
我当时截取到的日志大概是这种感觉:
[codex] request failed after 60s: timeout [codex] retrying in 2s... [codex] request failed after 60s: timeout [codex] retrying in 4s... [codex] request failed after 60s: timeout [codex] retrying in 8s...看到这段日志的时候我就明白了,它"卡住"期间根本没有在请求服务端,而是自个儿在数秒。你以为它在重连,其实它在睡觉。
提示:
RUST_LOG的日志级别可以按模块细化,比如只想看网络相关的日志,可以试试RUST_LOG=codex=debug或者RUST_LOG=codex=info。日志级别越高输出越详细,但也越刷屏,排查时建议先用debug,不够再看trace。
3.2 第二步:备份配置,安全修改
定位到问题之后,修改就很简单了。先把原始配置备份:
cp ~/.codex/config.toml ~/.codex/config.toml.bak然后编辑配置:
vim ~/.codex/config.toml在文件合适的位置加入超时配置和重试次数配置。以我的环境为例,最终生效的配置长这样:
model = "gpt-4.1" model_provider = "openai" [model_providers.openai] timeout_ms = 600000 request_max_retries = 1这里有个小细节:在不同的版本里,request_max_retries有些放在顶层,有些放在指定的 provider 段下面。如果你加了之后启动报错,就根据错误提示调整位置;如果完全不报错但也不生效,多半是字段名不对,查一下当前版本的配置说明再改。
3.3 第三步:复测,确认"一行配置"真的生效
改完配置后,重启 Codex,用之前最容易卡住的同一个任务做复测。我当时做了一个简单的对比,同样是一个长上下文重构任务:
| 场景 | 修复前 | 修复后 |
|---|---|---|
| 首次响应耗时 | 约 2 分钟 | 约 2 分钟 |
| 客户端是否触发超时 | 是 | 否 |
| 从开始到结束总耗时 | 超过 15 分钟,多次重连 | 约 2 分半,全程无中断 |
| 界面状态 | Reconnecting转圈 | 正常输出 |
修复前总耗时 15 分钟以上,其中绝大部分都耗在了退避重试上,真正的模型计算时间只有那 2 分钟;修复后整个流程一口气跑完,没有再出现Reconnecting状态。这就是流程对不上节奏导致的"假死"——不是模型慢了,是客户端没耐心等。
4. 改了配置之后,还会遇到哪些坑
4.1 改了配置还是卡?先看看上下文是不是撑爆了
有一次我改了超时配置之后,某个任务还是卡了很久。后来一查,根本不是客户端超时的问题,而是我一次性在 Codex 里引用了太多文件。上下文越长,模型需要处理的内容越多,首次响应时间自然就越长。哪怕我把超时调到了 10 分钟,如果一个请求本身就需要 15 分钟,那客户端还是会判定超时。
这种情况的解决办法不是继续调大超时,而是精简输入:少引用几个无关文件,把大任务拆成几个小任务分开做。Codex 这类工具和搜索引擎不一样,不是输入越多越好,输入越精炼,响应越快,也越不容易触发重连机制。
4.2 不卡了,但错误响应变慢?超时要和重试配合着调
把超时调到 10 分钟之后,我发现一个新的问题:当模型服务端真的出故障时,Codex 要等满 10 分钟才会报错,这个故障响应速度就太慢了。
解决办法是让超时和重试次数配合起来,不要单独拉一个。我最终采用的组合是:请求超时 10 分钟,重试次数只留 1 次。这样正常请求有足够时间跑完,真正出问题时也不会在故障里反复横跳太久。你还可以在启动 Codex 的终端外面套一层timeout命令,给整个会话设一个硬性上限,防止极端情况下进程真的挂死:
timeout 3600 codex这样即使 Codex 内部配置出了问题,最多 1 小时后也会被强制结束,不会真的"卡半天"。
4.3 配置不生效?先检查路径,再检查版本
如果你按上面的方法改完配置,发现重启后完全没有变化,大概率是下面两种原因之一。
一是配置路径不对。如果你设置了CODEX_HOME环境变量,Codex 会从这个变量指向的目录读取配置,而不是从默认的~/.codex读。你先执行echo $CODEX_HOME看看是不是指向了其他位置;如果是,就把配置复制过去,或者把环境变量清掉恢复默认路径。
二是字段名版本不支持。Codex 的版本更新比较快,配置字段时有调整。你可以用codex --version看版本号,然后在启动时留意有没有关于配置解析的错误输出。遇到不认识的字段,安全做法是先去掉,只保留确定生效的那几行配置。毕竟我们真正需要的可能就那一行超时配置,其他都是锦上添花。
我在实际使用中还有一个习惯:遇到重连卡顿,先看日志里到底有没有请求在发,再决定改什么。很多时候你以为的"卡死"其实是客户端在退避等待,这时候去怀疑密钥、删掉重装,都不对症。先把上面那两个配置改好,大多数"卡半天"的场景都能直接消失。希望这篇排障记录能帮你少走两天弯路。