
某天下午我在终端里敲下claude回车光标闪了两下正准备把一段两百行的重构需求贴进去屏幕上先甩出来一行红字Unable to connect to API (ECONNRESET)。再试一次变成API ERROR第三次干脆卡在Connecting...不动了。同一台机器、同一个终端、十分钟前还好好的。这种时候最容易犯的错就是立刻怀疑是不是服务挂了然后开始无脑重试重试十几次之后彻底放弃。实际上ECONNRESET这个错误码的信息量非常大它几乎在明明白白告诉你链路是通的握手是完成的是对端或者链路中途的某个设备在某个时刻把这条 TCP 连接掐掉了。搞清这一点排查方向立刻从网通不通切换到谁在什么时候掐的、为什么掐。这篇内容围绕 Claude Code 在真实使用中遇到的 API ERROR 与 ECONNRESET 两类报错展开讲清楚它们的层次差异、请求链路上每一道关卡各自的断连习惯、一套可以照着复现的四层排查流程以及让长请求不再被中途截断的参数调优和脚本兜底方案。无论你是刚装好 CLI 想跑通第一次对话还是已经用了几个月开始频繁撞墙下面这些排查路径和参数取舍都能直接拿去用。1. 报错文本背后的分层含义先把 ECONNRESET 翻译成人话1.1 RST 不是网络不通而是对面先挂了电话很多人把ECONNRESET和断网划等号这是排查跑偏的头号原因。从 TCP 层面看ECONNRESET对应的是一次RST报文的收发——连接已经建立三次握手早就完成数据也可能已经发出去了然后某一方或者链路上的中间设备发出了一个RST强制终止这条连接。换句话说电话是打通了的只是对方在你说话说到一半的时候把话筒扣了。这和ENOTFOUND、EAI_AGAIN域名根本没解析出来、ECONNREFUSED端口没人监听连接请求被直接拒绝、ETIMEDOUT长时间没响应是完全不同层面的问题。区分这几个错误码能省下大量瞎试的时间。我在实际排查里习惯先做一个错误码对照把症状和病灶位置对上号。下面这张表是我自己常用的一张速查表遇到报错先归类能直接把排查范围从整条链路缩到某一段报错片段出错层次典型成因第一优先检查项ECONNRESET连接已建立后被强制中断对端超时关连接、连接池复用失效连接、中间设备会话回收、请求体超限被截是否是空闲后首次请求ECONNREFUSEDTCP 握手阶段目标端口无监听、本地端口被占用、目标地址写错端点地址与端口是否可达ENOTFOUND/EAI_AGAINDNS 解析阶段域名拼写错误、DNS 服务器不可达、内网域名未配搜索域解析结果与 DNS 配置ETIMEDOUT传输或等待阶段出口被限速、链路质量差、对端处理过慢超时阈值与请求体大小UNABLE_TO_VERIFY_LEAF_SIGNATURE/SELF_SIGNED_CERT_IN_CHAINTLS 握手阶段中间设备做了证书替换本地缺根证书自定义 CA 证书配置API ERROR应用层服务端返回了非 2xx常见 429、5xx、请求体过大响应状态码与错误详情这张表的意义在于ECONNREFUSED要去查地址对不对、服务起没起而ECONNRESET要去查什么时候断的、断之前发生了什么。两者的动作完全不同搞混了就只能靠运气。1.2 API ERROR 与 Unable to connect 分属两个不同阶段Unable to connect to API (ECONNRESET)和裸的API ERROR看似都是连不上实际发生在完全不同的阶段。前者是传输层没走完客户端根本没拿到有效响应后者是请求已经发出去、服务端也回了东西只是回的不是成功状态。这个区别决定了你要不要去看请求内容本身。API ERROR后面通常会跟一个状态码或者一段服务端返回的错误描述。这一步非常关键很多人只看到API ERROR四个字母就开始重装 CLI其实往下几行就有更具体的信息。常见的情况有请求体太大被拒上下文贴了太多文件、短时间请求频率过高触发了限流、模型名称或参数不被接受、鉴权信息失效。这些都属于请求本身有问题跟网络半毛钱关系没有重装一百遍也不会好。Unable to connect则完全在网络与传输这一层。它说明客户端在建立连接、发送请求体、等待响应这几个动作中的某一个环节被打断了。我个人的经验是如果报错出现得非常快一两秒内多半是连接复用出了问题或者 TLS 握手失败如果是等了一段时间十几秒、几十秒才报那更像是某个环节的超时阈值到了或者请求体写到一半被掐。1.3 为什么交互式 CLI 比 curl 更容易撞上断连一个很反直觉的现象同一个端点在浏览器和 curl 里访问正常偏偏 Claude Code 一直报 ECONNRESET。原因在于这类交互式 CLI 的请求特征和普通 HTTP 请求差别很大。第一请求体大。一次对话请求里带着完整的上下文动辄几万 token序列化之后可能是几百 KB 甚至上 MB 的 JSON。大请求体意味着更长的上传时间也意味着更容易在传输过程中被中间设备的策略拦下。第二会话长。交互式使用是连续的一次任务可能产生几十次请求中间夹杂着用户思考、看代码、改需求的间隔这就产生了大量的空闲期而空闲期正是连接被中间设备回收的高发时段。第三连接复用。CLI 为了省掉反复握手的开销通常会保持长连接并复用一旦池子里的某条连接被对端悄悄关掉了客户端并不一定立刻知道下一次直接拿这条僵尸连接发请求收到的就是 RST报错就成了 ECONNRESET。这三条叠加起来就解释了为什么浏览器能打开、CLI 却报错。浏览器有更激进的连接管理策略和更完善的重试而 CLI 的行为取决于它底层用的 HTTP 客户端配置。2. 请求穿过的五道关卡以及各自的断连习惯2.1 从敲下回车到首字节返回的完整链路把一次请求拆开看它至少要穿过五道关卡本机应用层Node 运行时与 HTTP 客户端→ 本机操作系统网络栈含 DNS 解析→ 本机所在网络的出口设备 → 中间传输链路 → 目标服务前面的接入层负载均衡、网关→ 应用服务本身。任何一道关卡做了主动关闭的动作客户端看到的现象都可能是ECONNRESET。这个拆法的价值在于它能帮你判断该在自己机器上折腾还是该去找网络环境的管理方。我遇到过的情况里本机层面的问题占了大概六成配置残留、证书、DNS、Node 版本出口与中间设备占三成剩下的一成才是服务端本身。所以排查顺序一定是从近到远而不是一上来就怀疑远端。有一个很实用的判断方法准备一个极简的连通性测试用同样的运行时、同样的配置发一个最小请求。如果最小请求能通、大请求不通问题在体积和超时如果最小请求也不通问题在连接建立或配置。这一步能把排查空间砍掉一半。2.2 空闲连接回收最常见也最容易被误判的一种这是我见过最高频、也最容易被误判成服务不稳定的一类 ECONNRESET。表现特征非常明显连续对话时一切正常去开了个会回来或者切到别的窗口看了会儿文档回来发第一条消息立刻报错第二次发就正常了。原因就在于中间设备和接入层的空闲连接回收策略。为了节省资源链路上的设备会记录每条连接的最后活跃时间超过一定时间没有数据往来就主动关闭。问题在于关闭动作有时只发给了服务端而没同步到客户端或者客户端收到了 FIN 但没及时清理连接池。于是客户端认为这条连接还活着直接拿来发新请求服务端那边已经没这条连接了回的当然是 RST。针对这一类最直接的缓解手段是缩短客户端的连接复用时间让它别把连接留太久。如果你是用脚本调同类 APINode 的undici可以这样配——把keepAliveTimeout设得比链路上的空闲回收阈值更短让客户端主动先放弃旧连接比被动收到 RST 优雅得多import { Agent, setGlobalDispatcher } from undici; setGlobalDispatcher(new Agent({ keepAliveTimeout: 10_000, // 客户端 10 秒没动静就自己关抢在中间设备之前 keepAliveMaxTimeout: 30_000, // 单条连接最长存活时间 connect: { timeout: 15_000 }, // 建连超时 headersTimeout: 60_000, // 等响应头超时 bodyTimeout: 120_000, // 等响应体超时 }));这段配置的核心思路是主动比被动好。客户端自己关连接是干净的 FIN服务端不会产生异常等服务端或中间设备来关客户端拿到的就是 RST就变成了报错。用 CLI 的话不能直接改这段代码但可以理解这个机制从而知道空闲后首次请求失败根本不是硬件问题、也不是服务问题而是连接生命周期管理的问题。2.3 请求体体积与上下文长度带来的隐性压力第二类高频原因跟体积有关。Claude Code 的工作方式决定了请求体天然偏大它会把项目结构、相关文件内容、对话历史一起打包发出去。当你让它在多文件之间跳转分析、或者把几百行日志贴进去让它找问题时请求体可能瞬间涨到几 MB。体积带来两个后果。一是上传耗时变长整个请求处于传输中状态的时间窗口变宽撞上中间设备策略的概率上升。二是很多接入层对单请求体大小有硬性上限超了之后不是返回一个漂亮的 413而是在读请求体的过程中直接断连——客户端这时看到的就是 ECONNRESET而且往往发生在等了十几秒之后很容易被误判成服务端处理慢。我在实践中总结出一个很粗糙但好用的经验值单次请求携带的文件内容控制在合理范围内遇到需要分析大量代码的场景先用目录级概览让模型定位到具体文件再分批把文件内容送进去。这不是妥协反而是更高效的工作方式——把一次巨型请求拆成几次小请求既降低了断连概率也让模型的注意力更集中输出质量往往更好。3. 一套可复现的四层排查链路3.1 第一层用最小请求区分出不去和回不来排查的第一步永远是做减法。不要在一个复杂的交互场景里调问题先退到最小可复现单元。我的做法是在一个干净目录下用最短的提示词发一次请求把所有可能干扰的因素项目上下文、历史会话、自定义配置全部排除。具体动作是切到一个空目录运行一次最简单的单轮对话。如果这一步就报错说明问题跟上下文无关直接进入配置和链路排查。如果这一步正常那就逐项加回变量——先加项目上下文再加长历史再加大文件每次只加一个直到复现。这个二分加变量的过程看起来笨但它是唯一能确定因果关系的方法。还有一个小技巧把claude --version和node -v的输出先记下来。Node 版本对 HTTP 客户端行为影响很大Node 18 早期版本的fetch实现基于 undici在 keep-alive 和超时处理上有过若干已知问题版本太旧或者太新比如某些奇数版本都可能踩到边缘 case。把版本信息固定下来至少能保证你后面所有的对比测试是在同一个基线上做的。3.2 第二层把环境变量和配置文件的隐性残留清一遍这一层是我踩坑最多的地方。CLI 工具的行为高度依赖环境变量和用户级配置文件而这些地方最容易留下的就是历史残留——几个月前配过的东西早就没人维护了但每次启动都在生效悄悄地改变着请求的走向。排查方法是把当前 shell 里所有相关的环境变量列出来看一遍。重点看三类一是 API 端点相关的有没有被改成指向某个已经不存在的地址二是超时与重试相关的有没有被设成一个很小的值三是证书与 TLS 相关的有没有指向一个过期的证书文件。用env | grep -i配合关键词过滤比一个一个猜要快得多。配置文件也要看。Claude Code 的用户级配置一般在用户主目录下的隐藏目录里项目级配置可能在项目根目录。这两处可能同时存在配置且优先级不同。我遇到过的情况是项目目录里留着一份早期调试时写的配置指向了一个测试用的端点后来项目被复制到别的地方配置文件跟着走了于是新的环境里一直在往错误地址发请求。这种问题不看配置文件是绝对查不出来的。清理的时候遵循一个原则先备份再删一次只改一处。把所有可疑配置一次性清空虽然大概率能好但你永远不知道是哪一项起的效下次再遇到还是不会查。改成一次动一个改完立刻测虽然慢一点但能积累出真正有用的认知。3.3 第三层DNS、系统时间与 TLS 握手的三个硬条件过了配置这一关往下就是三个硬条件任何一个不满足都会导致握手失败或者连上就断。第一个是 DNS。检查方法很直接确认域名解析出来的地址是不是你预期的。在受管网络环境里内网 DNS 和公网 DNS 的解析结果经常不一样而 CLI 用的是系统解析器跟你在浏览器里看到的可能不同。如果解析结果指向了一个不通的地址表现出来可能是超时也可能是在某个中间设备那里被直接重置。第二个是系统时间。这个原因听起来很荒谬但 TLS 证书校验依赖时间戳系统时间偏差过大比如虚拟机休眠后时间没同步、或者时区配置错了导致偏差几小时握手阶段就会失败。有些实现下表现为证书错误有些实现下就表现为连接被重置。检查一下系统时间跟标准时间的偏差几秒钟的事但能排掉一大类玄学问题。第三个是证书链。很多受管网络环境会在出口做 TLS 检查意味着你实际握手的是中间设备而不是目标服务它出示的证书由企业自己的根证书签发。这时候需要在本地信任这个根证书Node 运行时可以通过NODE_EXTRA_CA_CERTS指定# 指向企业根证书PEM 格式仅对当前 shell 生效 export NODE_EXTRA_CA_CERTS/path/to/corp-root-ca.pem # 验证一下 Node 是否真的读到了 node -e console.log(process.env.NODE_EXTRA_CA_CERTS)注意网上有些偏方会建议关掉证书校验把NODE_TLS_REJECT_UNAUTHORIZED设为0。这个做法等于把传输层的身份验证完全放弃任何中间设备都可以冒充目标服务风险极高。正确的做法是把该信任的根证书装进信任链而不是把门拆了。3.4 第四层出口设备与中间节点的会话裁剪如果前三层都干净问题大概率在出口这一侧。这一层的典型特征是间歇性——不是每次都失败而是有规律地失败。比如每天早上第一次用必失败或者每连续对话超过二十分钟必失败一次。判断方法看时间规律。记录下每次失败的时间点和上一次成功请求的时间点算一下间隔。如果间隔固定在某个值附近比如 60 秒、300 秒那基本可以确定是某处设了固定的空闲回收或会话存活上限。这种规律性问题是最好解决的因为你知道阈值在哪只要让客户端的行为跑在阈值之内就行。另一个特征是特定操作必失败。比如每次贴超过某个大小的文件就断每次让模型读取某个特定目录就断。这说明中间设备在做内容层面的策略可能跟请求体大小、请求频率或者请求的路径有关。这种情况下把操作拆小、把频率降下来基本都能绕过。需要强调的是这一层的问题往往不在你能直接控制的范围内。你能做的是让客户端的行为更温和减少并发、拉长间隔、缩小请求体、缩短连接复用时间。这些调整不会让链路变得更好但能让你的请求更不容易撞在策略的枪口上。3.5 把证据钉死日志、版本与诊断命令排查过程中最忌讳的就是凭感觉改改完好了也不知道为什么好下次再犯还是不会查。所以每一步都要留下证据。Claude Code 提供了诊断入口/doctor会检查安装完整性、版本、配置状态等基础信息是排查的第一站。如果 CLI 支持详细日志开关不同版本可能是--debug参数或者对应的环境变量一定要打开把完整请求链路打出来。日志里能看到的关键信息包括实际请求的端点地址、DNS 解析结果、连接建立耗时、请求体大小、失败时的具体错误码和堆栈。有了这些你能明确区分根本没发出去和发出去了但没回来。我个人习惯把每次出问题的现场信息记成一个小卡片格式固定时间、CLI 版本、Node 版本、失败操作、报错原文、是否可复现、重试后是否成功。积累十几条之后规律自己就浮出来了。这个习惯帮我定位过好几次那种只在特定时段出现的问题光靠当场调试是查不出来的。4. 让长请求不再被中途截断的参数与工程手段4.1 超时与重试的取值逻辑超时参数的设置有一个常见的误区认为设得越大越安全。实际上超时设得过大反而有害——请求被中间设备掐断之后客户端还会傻等很久才报错用户体验极差而且超时太久会占着连接不放进一步加剧连接池耗尽的问题。合理的取值逻辑是分层设置、逐层递增。建连超时应该最短因为 TCP 握手正常情况下是毫秒级的事给 10 到 15 秒已经很宽容了等响应头的超时要长一些因为服务端要做推理首字节返回可能需要几秒到几十秒等响应体的超时要最长因为流式输出可能持续很久。三层的比例大概是 1 : 4 : 8。重试策略则要区分错误类型。ECONNRESET这类传输层错误重试是合理的因为下一次大概率会走一条新连接。API ERROR里的 4xx 类错误比如请求体过大、参数不合法重试毫无意义只会浪费时间。限流类的错误要退避重试而且退避时间要递增。判断错的类型再重试是自动化脚本里最容易忽略的一环。错误类型建议动作退避策略说明ECONNRESET重试立即或短退避1-3 秒换新连接大概率成功ETIMEDOUT谨慎重试指数退避连续超时说明链路有问题重试也是白等429 限流必须重试指数退避起步 5 秒以上越急越失败4xx 参数类不重试—是请求本身的问题重试没意义5xx 服务端重试指数退避最多 3 次服务端临时故障4.2 上下文瘦身把一次大请求拆成几次小请求这是我用得最多、效果最直接的一招。很多人习惯把整个需求一次性丢进去——帮我看看这个项目有什么问题然后让工具自己去翻。这种用法下请求体巨大、耗时长、断连概率高而且模型的输出往往也比较泛。改成两段式第一段只让模型看目录结构和关键文件清单让它自己指出哪些文件需要细看第二段只把那几个文件的内容送进去。这样单次请求的体积可能下降一个数量级断连概率大幅降低而输出质量反而更高因为模型的注意力集中在了真正相关的代码上。对于确实需要大段输入的场景比如让它分析一大段日志也可以切块。先送前一部分让它给出初步结论再送后一部分让它基于已有结论继续。这种分段落的方式模拟了人处理大信息量的过程不仅降低了传输压力也让每一轮的输出更容易验证。4.3 连接复用的取舍与 Node 侧参数如果你的用法是通过脚本调用同类 API比如做批量处理、自动化流水线那么连接复用策略就需要认真设计。复用的好处是省掉握手开销坏处是遇到僵尸连接时会直接失败。这是一个明确的权衡。我的建议是低频调用间隔几十秒以上干脆不复用每次新建连接代价是几十毫秒的握手换来的是完全避开空闲回收问题。高频连续调用间隔几秒则复用但要把keepAliveTimeout设得比链路上的空闲阈值更小主动关比被动断好。具体到 Node 环境undici的Agent提供了这几个可调参数前面第 2.2 节已经给过配置示例核心就是三个值keepAliveTimeout控制空闲多久关、keepAliveMaxTimeout控制单连接最长寿命、connect.timeout控制建连超时。还有一个容易被忽略的点连接池大小。默认情况下池子可能有若干条并发连接如果其中一条变成了僵尸后续请求可能还会被分配到它上面。可以在错误处理里加一个逻辑捕获ECONNRESET之后主动销毁当前连接池让下一次请求全新建立。这样虽然粗暴但在排查阶段非常有效能立刻验证死连接这个假设。4.4 用一个守护脚本把重试自动化对于必须长时间跑的任务最实用的做法是外面套一层重试。不是在代码里改而是用一个 shell 脚本把 CLI 包起来识别出可重试的错误就自动重来。这样既不侵入工具本身又能显著提高长任务的完成率#!/usr/bin/env bash # run-claude.sh —— 带可重试错误识别的包装脚本 set -u MAX_RETRY5 attempt1 while [ $attempt -le $MAX_RETRY ]; do output$(claude -p $ 21) status$? if [ $status -eq 0 ]; then printf %s\n $output exit 0 fi # 命中传输层断连类错误才重试 if printf %s $output | grep -qE ECONNRESET|Unable to connect to API; then wait$(( attempt * 5 )) echo [warn] 第 ${attempt} 次失败命中可重试错误${wait}s 后重试 2 sleep $wait attempt$(( attempt 1 )) continue fi # 其他错误如参数错误、鉴权失败直接透传不做无意义重试 printf %s\n $output exit $status done echo [error] 连续 ${MAX_RETRY} 次失败请人工介入 2 exit 1这段脚本有两个设计要点值得说。一是只对特定错误重试用grep -qE做白名单匹配其他错误直接透传退出码。这个设计避免了参数写错了还傻重试五次的尴尬。二是退避时间递增attempt * 5让间隔从 5 秒逐步涨到 25 秒给链路一个缓冲期。如果是限流导致的失败递增退避几乎是唯一有效的策略。提示把21保留住非常重要。CLI 的错误信息有时走 stderr 有时走 stdout只抓一个会漏掉关键判断依据导致脚本误判成功。5. 几个反直觉的案例与一页纸应急清单5.1 三个反直觉的真实案例案例一换了个工作目录就好了。某次排查所有配置都干净、网络也正常但在特定项目目录下必报错。最后发现是那个目录里有一份遗留的项目级配置指向了一个早期测试用的端点地址。因为项目是从别处复制过来的配置文件跟着一起过来了谁也没注意到。这类问题的教训是排错时一定要在干净目录里做对照实验否则你无法确定问题是环境级还是项目级。案例二时间同步问题伪装成网络问题。一台长期休眠的开发机唤醒后立刻报 ECONNRESET。各种网络检查都正常最后发现是系统时间落后了将近半小时。TLS 校验对时间敏感偏差过大时握手会在某个环节失败而失败的表现形式在客户端看来就是连接被重置。这个案例告诉我遇到什么都对但就是不通的情况先看一眼系统时间成本极低。案例三请求体太大报错却长得像网络问题。一次让工具分析大量的运行日志每次都断重试也没用。后来把日志切成三段分别处理一次都没断。原因不是网络差而是单次请求体超过了链路上某处的限制在传输过程中被直接切断。这类问题的识别特征是小请求永远成功、大请求永远失败跟时间无关跟内容大小强相关。5.2 受管网络环境下的注意事项在单位网络里用这类工具有几个点需要提前确认能省掉大量来回折腾的时间。首先确认本地信任的根证书清单。如果有中间设备做 TLS 检查它的根证书必须被本地运行时信任Node 环境要用NODE_EXTRA_CA_CERTS显式指定。这件事最好在第一次使用前就配好不要等到报错了才查。其次确认 DNS 解析结果。受管环境里内网 DNS 和公网 DNS 的解析可能完全不同如果发现解析结果不符合预期需要跟网络管理方确认正确的解析配置。这一步不要自己瞎改hosts文件改错了影响面更大。第三注意请求频率。共享出口环境下短时间高频请求容易触发整体限流表现出来就是一阵一阵的失败。批量任务一定要加间隔宁可跑得慢一点也不要一次性把请求打满。最后所有配置变更都要留记录。环境变量改了哪个、证书换成了哪个路径、配置文件动了哪一行写在你自己的笔记里。这类问题复发率不低有记录的话第二次处理只要五分钟。5.3 一页纸应急清单真出问题的时候人容易慌所以我给自己整理了一份从上到下照做的清单按顺序走一遍八成问题能定位记下报错原文的完整几行尤其是括号里的错误码不要只看第一行。在空目录下用最简提示词发一次请求确认是否与环境无关。检查claude --version与node -v记下来作为基线。列出当前 shell 里所有相关环境变量看有无指向失效地址的残留。检查用户级与项目级配置文件确认没有历史遗留的错误端点。核对系统时间偏差超过几分钟就要处理。确认自定义根证书路径存在且被正确加载。观察失败的时间规律判断是否与空闲时长相关。换一个小请求测试判断是否与请求体体积相关。打开详细日志看请求到底发到了哪个地址、卡在哪一步。用外面套重试脚本的方式跑一次长任务看能否绕过。以上都过一遍仍然失败再考虑把完整现场信息整理后反馈。这份清单的价值不在于每一步都多高明而在于它强制你按从近到远、从简到繁的顺序走避免一上来就做最复杂的操作。最后分享一个我在长期使用里形成的习惯把第一次失败和第二次失败严格区分开。第一次失败时不要急着改配置先原样重试一次观察是否自愈。如果自愈那大概率是空闲连接或瞬时抖动记一笔就行不用动任何东西如果连续两次都失败且错误码一致再按上面的清单开始排查。这个习惯帮我避免了无数次为了修一个本来会自己好的问题把好好的配置改坏了的情况。工具和人一样偶尔打个嗝很正常重要的是分清楚打嗝和生病。