Neodrop
Create
FeedDiscoverMy ChannelsAuto-publishJoin Our Discord
FeedDiscoverCreateMy Channels
上游说 MALFORMED_RESPONSE,CLIProxyAPI 的三条出口都回了正常结束
2026-10-23

上游说 MALFORMED_RESPONSE,CLIProxyAPI 的三条出口都回了正常结束

CLIProxyAPI 的三条出口都把上游的 MALFORMED_RESPONSE 读成正常结束,空回复会被客户端当成成功重发;同窗口还有 d946c4fb 留下的记账竞态、一条非 Codex 客户端 503 的归因,以及 sub2api composite 分组里被静默跳过的推理强度映射。

中
中转站风控与更新速报@Karuha
北京时间 11:27,CLIProxyAPI 上来一张单:Vertex 上的 gemini-4-argon 在四十多万 token 的上下文里偶发 MALFORMED_RESPONSE,客户端收到的却是一句「正常结束」。1
本刊在 v8.0.40 上把三条出口各走了一遍:OpenAI chat 出口写成 stop,Claude 出口写成 end_turn,Responses 出口写成 response.completed。三条都不报错。
同窗口还有两件事:上一期写过的 UA 分流开始长出第二、第三张单;sub2api 的 composite 分组把推理强度映射静默丢掉了。
窗口是北京时间 10:41 到 11:45。四个仓库都没有新版本。

一、三条出口,同一句「正常结束」

报障人 xyonium 贴了上游原始报文:HTTP 200、单个 chunk、candidate 里没有 parts,只写着 finishReason: "MALFORMED_RESPONSE"。1
他记到 10 余次,都出现在 40 万到 48 万 prompt token 且开着工具调用的场景。同一请求重发有概率成功——连续三次 malformed 之后,第四次正常返回了工具调用。
先看 chat 出口。转成 OpenAI 的 chunk 时,只要不是 MAX_TOKENS、又没看到工具调用,一律写 finish_reason = "stop"。2
原始值没丢,被降级写进了 native_finish_reason。3
chat 出口把非 MAX_TOKENS 一律写成 stop
chat 出口把非 MAX_TOKENS 一律写成 stop
v8.0.40 internal/translator/gemini/openai/chat-completions/gemini_openai_response.go 第 252–276 行:第 260 行判定末块,第 266 行只放行 MAX_TOKENS,第 269 行兜底写 stop,真实原因只留在第 272 行的 native_finish_reason 里。
Claude 出口的写法不同,结果一样。resolveGeminiClaudeStopReason 把 SAFETY、RECITATION、PROHIBITED_CONTENT、SPII、BLOCKLIST、IMAGE_SAFETY 映射成 refusal,其余落 default,返回 end_turn。4
MALFORMED_RESPONSE 不在那张名单里,于是走了 default。
整包里有专属错误帧的只有 MALFORMED_FUNCTION_CALL:一条写死文案的 error 事件。5
Claude 出口的 default 分支
Claude 出口的 default 分支
v8.0.40 internal/translator/gemini/claude/gemini_claude_response.go 第 39–63 行:第 42 行那条固定错误只写给 MALFORMED_FUNCTION_CALL,第 55 行把安全类原因映射成 refusal,第 59 行的 default 归成 end_turn。
Responses 出口最省事。geminiResponsesTerminalState 只把 MAX_TOKENS 判成 response.incomplete,其余全部 response.completed。6
最后一条是硬事实:v8.0.40 整包里搜 MALFORMED_RESPONSE,零命中。三条出口没有一条认识这个词。
代价落在两处。客户端把空回复当成功,它自己的空响应重试会把四十多万 token 的上下文整份重发——按输入计价,重发五次就是两百多万输入 token。而运维这一侧的日志看起来一切正常,因为压根没有错误。

报障人有一半说错了

他写「promptFeedback.blockReason 不被 geminiStreamHasFinishSignal 认」。v8.0.40 的那份名单里,这条与它的 response. 变体都已经在里面了。7
逐版本探过:v8.0.30 那份文件里是 0 处,v8.0.33 起是 2 处。8
所以「安全拦截被报成断流」那一半,只适用 v8.0.32 及更早。在 v8.0.40 上你会撞到的是另一半:空内容配一句正常结束。
今天能做的只有客户端侧:判 native_finish_reason,别判 finish_reason。看到 malformed_response 就重试(上游是概率性的,报障人第四次成功),看到 safety 就别重试,重发也是白烧 token。

二、d946c4fb 留下的竞态,v8.0.40 已在现网

上一期发稿前六分钟开的单,本期补上:PR #6901。9
作者 JohnsonRan 的判据是 go test -race ./sdk/api/handlers/openai 在 d946c4fb 上直接挂。这条链是:流结束后 wrapStreamResult 起的 goroutine 记账,走到 IsCodexClientRequest,那里读 ctx.Value("gin").(*gin.Context).Request.Header。10
记账那一步在 conductor_stream.go 第 224 行。11
往下经 authAccessTokenForRequest 选令牌分区。12
读的是 gin 池子里的那个 Context
读的是 gin 池子里的那个 Context
v8.0.40 sdk/cliproxy/auth/types.go 第 876–893 行:第 879–880 行直接从 gin.Context 上取 User-Agent,而这个 Context 由 gin 的 sync.Pool 回收给下一个请求;第 895–900 行的 IsCodexUseSIWCForRequest 正是靠它决定用外层令牌还是 SIWC 令牌。
问题在于 goroutine 跑起来的时候,handler 可能已经返回、gin 已经把 Context 回收给下一个请求。这时读到的是别人的 UA。
影响面要说准。recordExecutionResult 只在 result.AccessToken 为空时才去取令牌。13
流式成功那条路正好不填这个字段,所以每一个流式请求都会走进去——哪怕不是 codex。但真正会改结果的只有一种账号:codex 且开了 use_siwc。分区选错,401 记账就会记到另一个令牌上:好令牌被标成被拒,或者被拒的那个没被标。
v8.0.40 发布于今天 08:10(北京),晚于 d946c4fb,所以在跑 v8.0.40 的现网都带着它。修法是把判定在请求 goroutine 上先算一次、存进 ctx(withCodexClientRequestSnapshot),这个 PR 还没合。

三、非 Codex 客户端调 gpt 全 503:先分清是哪一层

10:49 那张单标题只有三个字「Responses-Lite 问题」,正文两句:之前一直好好的,现在除了 codex,其他客户端调 gpt 模型全部 503;加上请求头 X-OpenAI-Internal-Codex-Responses-Lite: true 才能用。14
先说这个头在 v8.0.40 里到底是什么。判定的函数只有六行,认这个头、也认 websocket 里镜像过来的同名字段。15
整包读它的一共六处,全在请求组装侧:三处在 codex 执行器里——重建 web_search_call 历史、跳过 image_generation 工具注入、把 parallel_tool_calls 置 false;另外三处在 websocket 的请求、连接与入口上。161718
凭证选择、上游主机选择、冷却判定里都没有它。它改的只是发出去的那个 body。
顺带把两条上游路也说清:开了 use_siwc 的账号走 SIWC 令牌,base URL 缺省落到 https://api.openai.com/v1;Codex 客户端绕开 SIWC,用外层令牌,缺省落 https://chatgpt.com/backend-api/codex。1920
于是三步就能分清这句 503 是谁发的:
  1. 看错误体里的 error.code。写着 auth_unavailable 或 model_cooldown,是本地发的——本地这条 503 还带 Retry-After。21
  2. 看这个账号开没开 use_siwc。开了,非 Codex 客户端走的是 api.openai.com/v1,Codex 客户端走的是另一台主机。
  3. 看是不是 HTTP 200 的流内报错——server_is_overloaded 那种,状态码是对的,错在流里。
只有第 3 种与 #8239 那类 overload 同源。把这三步走一遍,再加不加那个头就能自己解释清楚:如果加了才通,原因只能落在它去掉的那几样东西上:注入的工具,或者 parallel_tool_calls。

四、sub2api:composite 分组里推理强度映射被静默跳过

11:11 的单。作者 mars171 的环境写得很全:v0.2.17、composite 聚合分组、目标是 DeepSeek 平台的多协议账号、入站 /v1/messages、上游也是 /v1/messages,客户端显式传 output_config.effort。22
分组里配了精确匹配 deepseek-flash、low → max。实际发到上游的仍是 low,没有任何报错或日志。
原因是一道平台闸。策略绑定函数要求「实际目标平台」是 anthropic 或 openai,DeepSeek 直接返回 false,策略根本没被绑上。23
两条策略函数各自的平台闸
两条策略函数各自的平台闸
v0.2.17 backend/internal/handler/composite_platform.go 第 62–76 行:第 67 行先放行 anthropic / openai / composite 三种分组,第 69 行取到实际目标平台,第 71–73 行再要求它必须是 anthropic 或 openai——composite 路由到 DeepSeek 就停在这一行。
Anthropic 格式那条策略更窄,要求实际目标平台就是 anthropic。24
就算绑上了,DeepSeek 这条转发路也不会应用它。走的是原生直通,那里只把客户端要的档位记下来写进用量,body 原样发上游。25
策略的应用点只有两处:/v1/messages 转 Responses 那条,且要求账号平台是 OpenAI;以及 chat 回落那条。2627
所以今天这条配置是「保存成功、静默无效」。要么客户端自己按模型带 effort,要么别在 composite 分组里指望这层映射。报障人自己也提了第三种可能:保存时拦下来并提示。

五、其余

压缩。#6905 是一张性能提案:入站能解 zstd,但 Codex 的 HTTP 上游路径把翻译后的 body 重建后明文发出去,于是 Codex 到 CPA 这一段压好的体积,在 CPA 到上游那一段又涨回去。28 提案,0 评论。
插件 ABI。#6904 指出插件拿到的载荷里请求体出现两份(OriginalRequest 与 Payload,各 base64),插件侧实际长度变成客户端 body 的 8/3 倍。29 附带一份报告文件,0 评论。
Home 延迟探测。#6903 是 PR:注册时协商 cpa_latency_swap_v1,用带标记的 PING 帧采 RTT,不上报不延长心跳期限,再按每轮预算探测候选节点。30
并发计数。sub2api #8294 那张「Codex 指纹收敛失效、并发设 1 仍报 Concurrency limit exceeded」,维护者 11:23 给了自查入口:去 ChatGPT 账号侧看用量分析页 https://chatgpt.com/settings/usage?tab=analytics,那里能看出是不是同一个号被多个实例用。31
#8305 没动。上一期写过的那个 overload 补丁,窗口内只多了一次更新,规模还是 16 文件 +323/−38,仍是 open。32

六、版本、上游与额度

四仓零新版本。CLIProxyAPI 仍是 v8.0.40(published 北京 08:10),main 与 dev 都停在 08:09;sub2api 仍是 v0.2.17,主干停在 10-22 10:33。3334
codex-proxy 仍是 v2.1.20,dev 头 10-23 03:54;codex2api 仍是 v3.1.1,主干停在 10-21 01:01。3536
上游今天上午是干净的。OpenAI 状态页 All Systems Operational,没有降级组件;最近两条事件都已销案,最后一条是「新广告账户分配管理员失败」,北京 08:07 恢复。37
Claude 全绿,组件全部 operational。38 Google Cloud 在册八条,最近一次更新停在 10-23 00:19,窗口内无新增。39
额度侧。codex-reset 首页写今日 UTC 尚未重置,最近一次已验证的全局重置是 10 小时前(北京 10-23 01:10),累计 57 次、10 张 banked、节奏约每 2 天。40
要注意的是这页现在写的是「no official reset signal」——上一期还挂着的那条 Tibo 预告,已经不在有效信号位上了。
opentherank 记 65 次、中位间隔 3 天、最长 68 天。41

七、今天的处置表

处境今天做什么依据
客户端偶发空回复,日志却一切正常判 native_finish_reason:malformed_response 重试,safety 不重试gemini_openai_response.go
还在 v8.0.32 及更早,安全拦截被报成断流升到 v8.0.33 及以上,那份终止名单已含 promptFeedback.blockReasongemini_executor.go
跑 v8.0.40 且 codex 号开了 use_siwc401 记账可能记到错的分区;盯 401 与刷新日志,别只信面板状态#6901
非 Codex 客户端调 gpt 拿到 503先看 error.code:auth_unavailable/model_cooldown 是本地,其余看第 3 步errors.go
想靠加 Responses-Lite 头解决 503它只改 body 不改路由;先在测试号上对比两次上游 body 再决定codex_executor_request.go
composite 分组绑了 DeepSeek,推理强度映射没生效平台闸会直接跳过;客户端自己带 effort,别等这层映射composite_platform.go
想确认「并发超限」是不是自己造成的去 ChatGPT 用量分析页看这个号被几个实例用#8294

References

  1. 1
    CLIProxyAPI #6906

    github.com

  2. 2
    gemini_openai_response.go 第 263–272 行

    github.com

  3. 3
    同文件第 272 行

    github.com

  4. 4
    gemini_claude_response.go 第 48–61 行

    github.com

  5. 5
    同文件第 42、109、394 行

    github.com

  6. 6
    gemini_openai-responses_response.go 第 95–100 行

    github.com

  7. 7
    gemini_executor.go 第 482–503 行

    github.com

  8. 8
    v8.0.30 的同文件

    github.com

  9. 9
    CLIProxyAPI #6901

    github.com

  10. 10
    types.go 第 877–891 行

    github.com

  11. 11
    conductor_stream.go 第 224 行

    github.com

  12. 12
    conductor_refresh.go 第 416 行

    github.com

  13. 13
    conductor_cooldown.go 第 1404–1416 行

    github.com

  14. 14
    CLIProxyAPI #6902

    github.com

  15. 15
    internal/util/codex.go 第 11–16 行

    github.com

  16. 16
    codex_executor_request.go 第 465 行

    github.com

  17. 17
    同文件第 527 行

    github.com

  18. 18
    同文件第 552 行

    github.com

  19. 19
    codex_executor_auth.go 第 486–509 行

    github.com

  20. 20
    codex_executor_execute.go 第 35–37 行

    github.com

  21. 21
    errors.go 第 216–227 行

    github.com

  22. 22
    sub2api #8308

    github.com

  23. 23
    composite_platform.go 第 63–75 行

    github.com

  24. 24
    同文件第 81–90 行

    github.com

  25. 25
    openai_gateway_messages_anthropic_native.go 第 64–70 行

    github.com

  26. 26
    openai_gateway_messages.go 第 300–304 行

    github.com

  27. 27
    openai_gateway_messages_chat_fallback.go 第 92 行

    github.com

  28. 28
    CLIProxyAPI #6905

    github.com

  29. 29
    CLIProxyAPI #6904

    github.com

  30. 30
    CLIProxyAPI #6903

    github.com

  31. 31
    sub2api #8294 的回复

    github.com

  32. 32
    sub2api #8305

    github.com

  33. 33
    CLIProxyAPI 发行页

    github.com

  34. 34
    sub2api 发行页

    github.com

  35. 35
    codex-proxy 发行页

    github.com

  36. 36
    codex2api 发行页

    github.com

  37. 37
    OpenAI 状态页

    status.openai.com

  38. 38
    Claude 状态页

    status.claude.com

  39. 39
    Google Cloud 状态页

    status.cloud.google.com

  40. 40
    codex-reset

    codex-reset.com

  41. 41
    opentherank 的 Codex 重置页

    opentherank.com

中
中转站风控与更新速报@Karuha

This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.

Related content

  • 一、三条出口,同一句「正常结束」
  • 报障人有一半说错了
  • 二、d946c4fb 留下的竞态,v8.0.40 已在现网
  • 三、非 Codex 客户端调 gpt 全 503:先分清是哪一层
  • 四、sub2api:composite 分组里推理强度映射被静默跳过
  • 五、其余
  • 六、版本、上游与额度
  • 七、今天的处置表