Codex CLI 五次重连问题解析:从空闲超时到自动重跑的完整方案
最近在跑 Codex CLI 的代码生成任务时被“五次重连”折磨了一整天。任务在终端里跑跑到一半提示连接断开接着工具自动重连恢复了再跑一会又断开又重连……如此反复第五次工具直接放弃任务失败退出。最崩溃的是前四次的“假成功”让你根本不敢手动中断只能眼睁睁看着它消耗时间。我仔细翻了一遍日志发现这个场景有几个共性一是任务都偏长持续十几分钟以上二是断开发生前通常有较长一段静默期也就是工具在思考或者执行批量操作时没有产生新消息三是前四次恢复后会话里的上下文都还在等到第五次才彻底丢失。这基本可以判断不是单纯的网络故障而是空闲超时和会话状态管理的问题。这篇文章把 Codex 重连问题从原因到解决方案完整拆一遍包括网络链路怎么排查、CLI 的会话参数怎么调、tmux 怎么保活、自动重连脚本怎么写以及一份可以直接照着抄的问题速查表。适合正在用命令行 AI 编码工具的人尤其是那种一跑就是十几分钟的长任务场景看完能帮你少踩不少坑。1. 先把“Codex五次重连”这事说清楚1.1 触发场景往往不是偶发网络抖动我在项目里主要用 Codex CLI 做自动化编码比如根据任务描述自动生成模块、批量重构旧代码、写测试用例。这类任务有个共同点单次执行时间长而且中间会出现十几秒甚至几分钟的“沉默期”——工具在思考、在编译、在跑测试时不会发送新内容到终端。问题恰恰出在这个“沉默期”上。正常来说一个 HTTP 长连接如果超过一定时间没有数据传输中间链路设备会回收连接。等你下一次发数据时连接已经不存在了工具这边就报“连接断开”。Codex CLI 检测到断开后会按照内置的重试策略自动恢复第一次重连成功后继续干活然后隔一段又断再重连。如果连续断开的次数达到了工具默认的重试上限通常在第五次左右整个任务就直接判定失败退出码变成非零。所以“五次重连”本质上不是五次故障而是同一种空闲断连问题反复发生五次直到耗尽重试额度。理解这个逻辑是整个方案的关键。1.2 三层因素叠加单改一个地方没用既然断连是因为“空闲超时”对应的解决思路自然是让连接别闲着。但实操下来问题往往不是单点原因而是三层因素叠加在一起。第一层是本地接入链路。很多开发机通过公司出口或本地网关访问外部服务网关的 NAT 表项有回收时间短则 60 秒长则 300 秒。如果表项被回收后面的数据包就过不去了连接看似断开。第二层是终端会话本身。如果你在 SSH 里直接跑 Codex前端会话一断后面的进程会收到 SIGHUP 信号任务直接终止根本等不到重连。第三层是工具自身的会话策略。Codex CLI 对长任务的保活参数、重试次数、超时阈值都有默认设定但默认值不一定适配十几分钟的长任务。这三层只要有一层出问题任务就跑不稳。我之前只调参数不保活终端结果前端 SSH 一断任务照样死后来把终端保活做了但网络链路没优化重连照样频繁。所以说这套方案必须端到端一起处理。2. 关键变量到底卡在哪2.1 空闲超时最容易被忽略的默认值很多人在排查网络问题时会去看宽带质量却忘了检查设备链路层的空闲超时时间。家庭路由器、公司网关、云上跳板机几乎所有网络设备都会配置一个“超时回收时间”。这个值是设备级的通常不会在应用日志里出现。我做过一个很直接的实验同一个 Codex 长任务在本地直连网络下跑断连次数明显小于经过多重网关的路径。后续查日志发现断开时间点非常精准基本都落在静默期超过 75 秒左右这和常见的 NAT 超时值区间高度吻合。所以从客户端角度看你什么错误都看不到只知道连接断了其实是链路上某个节点把连接“遗弃”了。要验证是不是这个问题先别急着改代码。跑一个长任务记录断开时间点再看断开前有没有较长的无消息间隔。如果每次都出现在静默之后大概率就是空闲超时的问题。2.2 重试策略工具如何决定“放弃”Codex CLI 的连接层自带重试机制。我观察到的行为是这样的连接断开后工具会尝试恢复会话恢复成功就继续失败则重试。默认情况下连续多次失败后会放弃整个任务这个次数通常是五次左右。这里有个坑前几次重连失败不会立刻终止任务终端还会显示一些类似“重连中”的提示。但如果你没有看日志的习惯光盯着终端输出很容易被“还在转”的状态误导。等到第五次失败任务直接退出之前所有现场都没了。解决办法分两层。第一层是减少断开次数这是治本。第二层是让重试次数和间隔更符合长任务场景比如增加单次任务的总重试次数、拉长重试间隔、开启自动恢复。具体能不能自定义取决于工具的版本和配置项但至少你可以通过外部脚本包一层“失败自动重新执行”避免第五次失败后束手无策。2.3 本地接入链路的断流与丢包链路断流和丢包是两个不同维度的问题。丢包表现为命令跑着跑着返回结果特别慢超时后报错。断流则表现为一切看起来正常但下一句话就是“连接已断开”。我实际遇到过一种情况本地网卡开启了省电模式空闲一段时间后网卡进入低功耗状态此时连接明明还在但数据包发送延迟高到超时。关掉网卡的省电模式后这个问题立刻缓解。在 Linux 上可以用 ethtool 查看和关闭节能参数macOS 和 Windows 也有对应的电源设置这些都属于本地接入链路优化的范畴。另外某些环境需要通过 HTTP 代理接入外部服务。代理服务器同样有空闲超时如果代理的回收时间比应用层超时时间短就会出现“看起来完全正常但频繁掉线”的情况。排查方式很简单在不通过代理的情况下直连跑一次长任务对比断连频率差异一眼就能看出来。3. 端到端实操一套管用的重连方案3.1 方案总览三管齐下我把方案分成三个层面来落地进程保活层让 Codex CLI 跑在独立终端会话里前端断了任务也不受影响。链路保活层通过合理的链路配置减少空闲超时断连。任务自愈层写一个外层脚本检测到失败后按策略自动重新执行任务。这个思路解决了三个问题第一任务不会被 SIGHUP 杀掉第二断连频率降到最低第三万一最终还是失败了脚本兜底重跑不会让长任务白跑。以下是一个可以直接用的组合方案我用它跑通了一个持续 40 分钟以上的跨平台系统代码生成任务。3.2 持久化终端tmux 保活会话第一步是让 Codex CLI 的进程脱离当前终端跑在 tmux 会话里。# 创建名为 codex-run 的会话 tmux new -s codex-run # 在会话里启动任务 codex exec 根据 spec.md 生成用户模块的完整实现 \ --output-style full \ --model codex-latest run.log 21 # 按 Ctrlb 然后 d 退出会话任务继续在后台跑tmux 的好处是即使你的 SSH 断了、终端窗口关了会话里的进程依然活着。你随时可以重新附着tmux attach -t codex-run如果本机连一个 tmux 都不想装可以用 nohup 代替nohup codex exec 生成测试用例 run.log 21 这两者都能避免“终端窗口一关任务就跟着死”的悲剧。注意运行完任务后的日志一定要重定向到文件否则后续排查连证据都没有。3.3 链路与会话参数让连接别那么“脆弱”链路层面重点做三件事。第一如果你的环境有代理配置检查代理服务器的空闲超时时间。能调就调长不能调就改用直连方式跑长任务。第二操作系统层面开启 TCP 保活让系统定时发送 keepalive 包。Linux 下的 sysctl 配置示例# 开启 TCP keepalive默认关闭 sysctl -w net.ipv4.tcp_keepalive_time60 sysctl -w net.ipv4.tcp_keepalive_intvl30 sysctl -w net.ipv4.tcp_keepalive_probes5这段配置的意思是连接空闲 60 秒后系统开始发送探测包每 30 秒一次最多发 5 次。这样网络中间设备就不会因为长期无数据而回收连接。keepalive 包本身就是数据包几十字节对流量影响微乎其微但对长任务来说它像心跳一样让链路保持活跃。我在实际环境中调整后断连次数从一小时内四五次降到零或一次效果非常明显。如果 Codex CLI 本身支持配置超时或心跳间隔建议一并调整。比如把客户端超时时间设成 600 秒避免因为偶发延迟导致误判。具体配置要通过codex --help或配置文件确认不同版本差异较大但思路是通用的。3.4 自愈脚本失败自动重跑有了 tmux 和链路优化断连概率已经大幅下降但依然可能因为服务端异常、任务内容触发审核等原因失败。我写了一个外层包装脚本检测退出码非正常退出就重试并采用指数退避策略避免对服务端造成压力。#!/bin/bash # codex-retry.sh # 用法: ./codex-retry.sh 要执行的任务描述 输出目录 TASK_DESC$1 OUTPUT_DIR$2 MAX_RETRIES5 BASE_DELAY10 mkdir -p $OUTPUT_DIR for attempt in $(seq 1 $MAX_RETRIES); do echo [$(date %Y-%m-%d %H:%M:%S)] 第 $attempt 次尝试开始 codex exec $TASK_DESC \ --output-style full \ --log-file $OUTPUT_DIR/attempt_$attempt.log 21 exit_code$? if [ $exit_code -eq 0 ]; then echo [$(date %Y-%m-%d %H:%M:%S)] 执行成功退出码 0 exit 0 fi echo [$(date %Y-%m-%d %H:%M:%S)] 退出码 $exit_code准备重试 # 指数退避: 10秒、20秒、40秒、80秒不无限重试 sleep $((BASE_DELAY * 2 ** (attempt - 1))) done echo 达到最大重试次数 $MAX_RETRIES任务失败 exit 1指数退避为什么重要如果你重试太频繁服务端可能把你判定为异常请求反而会拉长限制时间更麻烦。初始 10 秒、翻倍到 80 秒总共 5 次节奏够稳也不会让服务端限流触发。脚本里的日志文件是独立的每次重试都单独落一份方便事后定位到底是哪一步出了问题。我自己跑长任务时90% 的情况不会再触发第五次因为前面链路和会话保活已经生效。4. 实操日志一次完整的演练记录4.1 环境信息与任务背景这次演练是一个跨平台系统的“代码生成与自测”任务具体是让 Codex CLI 生成一组用户权限管理模块覆盖前端的表单校验、后端的路由鉴权、数据库的模型定义。任务描述文件约 1200 行单次运行时间预计 25~40 分钟。环境情况开发机通过本地网关接入外部服务网关 NAT 超时约 120 秒。本机系统为 Linux终端工具支持 tmux。网络链路稳定不需要通过代理。Codex CLI 版本为当时最新稳定版。4.2 操作过程我先用 tmux 起了一个会话并在会话中执行包装脚本。日志文件实时输出到logs/目录。tmux new -s codex-task # 在 tmux 会话里执行 ./codex-retry.sh \ 根据 spec.md 实现用户权限管理模块包含前端校验、后端路由和数据库模型定义 \ logs/在脚本运行过程中我做了两件监控的事第一每隔两分钟看一眼logs/下新增日志文件的时间戳第二用tmux list-sessions确认会话没有被意外退出。任务跑到第 18 分钟时出现了一次连接断开日志里能看到“连接超时尝试重新连接”的提示。紧接着脚本检测到退出码非零进入第一次重试等待。我等了 10 秒后第二次尝试开始执行。由于链路 keepalive 已经配置好第二次执行没有再次出现断开直接跑完后续部分整个任务在第 36 分钟结束退出码 0。4.3 验证结果与复盘最终日志显示第一次尝试失败第二次尝试成功属于“一次重连即恢复”的理想情况。对比优化前的表现同类型任务基本都会在第五次重试时彻底失败而且失败后没有日志留档只能手动重跑。这次因为链路保活和自愈脚本任务没有白跑而且每次失败都有独立日志能清楚看到失败发生在哪个阶段。为什么要单独强调日志因为 Codex CLI 执行长任务时输出信息很容易被终端的重连提示刷屏真正有价值的错误信息反而被淹没。有了独立日志每次重试都是干净的输出文件定位问题效率高很多。5. 常见问题排查速查表5.1 任务执行到一半连接断开终端提示“重连中”这种情况最常见的原因是空闲超时优先检查链路层的 NAT 回收时间和 TCP keepalive 配置。另一个隐蔽原因是终端会话本身不稳定前台直接跑 Codex 的进程会随着 SSH 断开被 SIGHUP 杀掉。推荐先把任务挪进 tmux再配置 TCP keepalive再考虑是否调整工具本身的心跳参数。5.2 每次重连都成功但第五次还是会失败说明前四次重连只是“恢复连接”没有解决“空闲超时”的根因。每次重连成功后又进入长时间的静默然后再次触发同一个超时逻辑。解决办法是让静默期不静默——开启 keepalive或者把任务拆分成更短的子任务分批执行。不要一味调大重连次数治标不治本。5.3 状态码非零但日志里看不出明显报错排查时不要只看最后几行。打开完整日志搜索“timeout”“reset”“retry”“429”“500”这些关键词往往能找到真正的线索。如果没有这些词可以确认一个基础情况同一个任务在小模型上是否更容易失败是否因为任务描述过长导致请求体超过限制。如果是请求体超限需要把任务描述拆小而不是继续重试。5.4 速查表现象可能原因处理优先级连接断开频繁但任务可恢复链路空闲超时NAT 回收连接开启 TCP keepalive调整链路超时断连后终端进程消失SSH 断开导致 SIGHUP改用 tmux 或 nohup 保活每次重连成功后又断直到第五次失败空静默期过长未触发保活拉长客户端超时阈值增加心跳重连后任务已丢失上下文工具判定会话失效需重新执行调用自动重跑脚本传入相同任务描述任务长时间无响应最终提示超时网络丢包或服务端响应慢检查本地网卡省电模式必要时走直连这套速查表是我自己反复遇到的问题汇总不一定覆盖所有场景但能覆盖大部分导致“五次重连”的常见情况。6. 最后一点实操体会折腾完这一轮我最大的体会有两条。第一条遇到连接类问题时先改“环境”再改“工具”。很多人第一时间想到的是调整 Codex 的重试次数或者换更大的模型但其实大多数重连问题的根源都在接入链路的空闲超时和终端会话未保活上。环境调好了根本不需要那么多次重试。第二条长任务一定要有“失败后可复现”的能力。别把任务挂在裸终端里也别指望一次就能跑通。tmux 保活、日志落盘、指数退避重试这三件套几乎可以用在任何长任务场景里不仅仅限于 Codex。跑失败了看一眼日志改掉问题再自动重跑一次远比守在屏幕前反复按回车强。另外分享一个已经见效的小技巧如果你的任务描述特别长而且经常在加载阶段超时试试把描述里的示例代码拆出去用--file之类的参数单独传递内容请求体会明显缩小超时概率也会降下来。这个改动配合上面的保活方案我目前跑过的长任务基本能做到一遍过最差也只是自动重试一次。