
v8.0.53 里每条流式 Claude 请求都会重开一条上游连接:流末两处 break 加上一个只 Close 不排空的收尾,补丁还开在 dev,附带一分钟自查法与打补丁时要避开的 defer 顺序坑。
stream: true 的 Claude 请求一条一条地重开上游连接。api.anthropic.com 之间抓了包:连续四条请求(非流式、非流式、流式、流式),修之前用掉两条连接,修之后只有一条。1message_stop 就跳出读循环,剩下的响应体还没读完就关掉。net/http 只有把 body 读到 EOF 才把连接还回空闲池,没读完就 Close 等于真关一条连接。| 你在监控里看到的 | 在 v8.0.53 上它意味着什么 | 代价落在哪里 | 今晚能做的 |
|---|---|---|---|
| 流式请求一条一条新建上游连接 | 不是限流、不是账号问题,是收尾没读完 body | 每条请求一次 TCP + TLS 握手 | 自己打补丁,或不动 |
| 并发一高连接数就线性涨 | 同上,连接数跟 QPS 同阶 | 文件描述符、出口 NAT 端口、握手 CPU | 同上,先算 FD 与端口余量 |
| 非流式请求连接是复用的 | 符合预期,缺陷只在 stream: true | 无 | 不用管 |
| 切换到 Codex 上游就正常了 | 那边按设计每请求一条连接,不受这条影响 | 无 | 不用管 |

defer close(out) 先注册,第 350–355 行才注册关 body 的 defer,两者之间没有任何一次读。仓库 router-for-me/CLIProxyAPI,tag v8.0.53,文件 internal/runtime/executor/claude_executor_stream.go 第 343–358 行。2defer close(out),第二件才是关 body 的 defer。关之前,没有"把剩下读完"这一步。internal/runtime/executor 生产代码里,io.Discard 零命中——只有测试文件里出现过。这条链路上从来没写过排空。
if upstreamCompleted || upstreamStreamErr != nil { break } 是 Claude 直通分支,翻译分支第 530–531 行是同一句。仓库 router-for-me/CLIProxyAPI,tag v8.0.53,同文件第 423–438 行。4upstreamCompleted 在收到 message_stop 那一刻就置真。语义结束了,HTTP 响应没结束。0\r\n\r\n 还在路上;走 gzip 的话,后面还有一个 trailer。两者都没读,defer 就跑完了。io.ReadAll(decodedBody) 读到 EOF,再 Close。5message_stop 事件与 chunked 终止符通常分两段到,代理在第一段就跳出了。1stream 依次是 false、false、true、true。close_notify + FIN;第四条另开一条。1ss -tan state established '( dport = :443 )' | wc -l
tcpdump -n -i any 'tcp[tcpflags] & tcp-syn != 0 and host api.anthropic.com'
io.Copy(io.Discard, io.LimitReader(httpResp.Body, 64<<10)),外面套一层 ctx.Err() == nil。来自 PR 7237 头分支 mschirrmeister/CLIProxyAPI 的 fix/claude-stream-keepalive,文件 internal/runtime/executor/claude_executor_stream.go 第 346–363 行。6if ctx.Err() == nil {
_, _ = io.Copy(io.Discard, io.LimitReader(httpResp.Body, 64<<10))
}defer close(out) 注册在前,排空的 defer 注册在后,所以排空先跑、close(out) 后跑。defer close(out) 挪到排空 defer 之后再注册,让它先跑;二是让通道关闭与排空彻底解耦,排空放去别的 goroutine。
scanLoop:,第 572–575 行遇到空行时调 processFrame(),返回真就 break scanLoop,收尾靠函数级 defer httpResp.Body.Close(),中间同样没有排空。仓库 router-for-me/CLIProxyAPI,tag v8.0.53,文件 internal/runtime/executor/openai_compat_executor.go 第 560–580 行。9[DONE] 与最后一块是否落在同一次读里。dev 上也没有。v3.1.2 还是 10-28 那一版,窗口内零提交。13#7175(muse-* 的 client_attestation_denied)11-01 就以 completed 关了,维护者在这个窗口里补了一句"已在 dev 由 #7113 修"。14EnsureMetaMuseToolNamespaceDoc 在 v8.0.52 和 v8.0.53 的 meta_executor_execute.go 里都已经在了。手上是这两版的人,不用动。signal.active 为 false,最近一条被标成"新帖"的其实是雷达区的闲聊,不是重置承诺。18defer close(out) 的注册顺序一起调,或者让排空离开这个 goroutine。只抄七行的话,长连接上会多一个挂死的可能。dev,合了还要等下一次 tag;现在 dev 上也没有,别以为切过去就有了。github.com
github.com
github.com
github.com
github.com
github.com
github.com
github.com
status.openai.com
status.claude.com
status.cloud.google.com
codex-reset.com
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.