Codex 对话的流式响应被 keepalive 帧打断,导致每轮几乎必然中断并自动续跑
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 395
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
**提交人**: caowei1
**客户端版本**: 0.1.76
---
## 现象
Codex 对话(provider `cindy_codex`)在一段时间内几乎无法完成任何一轮:用户看到的是模型一直转圈 / 卡住,新消息发不出去,看起来像“连不上 AI 服务器”。客户端会自动续跑,但续跑后立刻再次卡住,形成循环。
## 失败链条(客户端日志)
1. `stream disconnected before completion: stream closed before response.completed`
2. Codex 自动重试:`stream disconnected - retrying sampling request (n/5 in ...ms)`
3. 重试耗尽 → 该轮 `turn_aborted`,reason=`interrupted`
4. 客户端判定 `Codex reconnect-stall`,触发 `interrupted-turn auto-resume`
5. 续跑后同一位置再次被掐断,回到第 1 步
## 关键节拍:keepalive 帧拖垮 SSE 流
SSE 流里存在 Codex 客户端不认识的 `keepalive` 事件,约每 30 秒一次;在第二次(有时第三次)keepalive 之后紧跟一次 SSE 解析失败,随后流被关闭。典型节拍:
```
15:54:12 keepalive
15:54:42 keepalive (+30s)
15:54:43 Failed to parse SSE event error_category=Data payload_bytes=149
15:54:43 stream closed before response.completed
```
同一形态在 16:21 / 16:32 / 16:36 / 16:44 / 16:49 / 16:52 / 17:01 / 17:05 反复出现,间隔恒定约 30 秒。日志中同时存在大量 `unhandled responses event: "keepalive"`。
## 量级与影响
- 一天内 `stream disconnected before completion` 出现 82 次(前两天分别为 7 次 / 3 次)
- `Failed to parse SSE event` 56 次(payload 149 / 328 字节两种)
- 客户端不认识的 `keepalive` 事件 91 次,全部集中在当天下午
- 某个 Codex 对话当天 11 轮全部 `turn_aborted`,0 轮完成
- 用户当天在该对话里连续多次反馈“又卡了”,最后不得不换 agent 才能继续工作
## 反证:不是本机网络问题
- 同一时间对 Codex 内部网关直连实测:TCP 建连 5–16ms、TLS 1.3 握手 41ms、未鉴权请求 29ms 返回 401(服务侧响应正常),说明链路可达
- 同一时间段 pi / deepseek 路由(litellm-chat-completions)请求全部成功,Claude 用量也在正常刷新
因此怀疑问题出在 `cindy_codex` 的流式桥接层:Codex CLI 的 base_url 指向本机动态端口的 `/responses`,由该桥接层/内部网关注入的 keepalive 帧格式与 Codex Responses 的 SSE 解析器不兼容。只要首字延迟超过 ~30 秒(reasoning effort 为 xhigh、上下文很大时很常见),就会命中 keepalive 分支并被误判成流结束。
## 实际故障环境
- Agent:Codex(不是提交本 issue 时使用的 agent)
- 模型:`codex/gpt-5.6-sol`
- 推理强度:xhigh
- 会话特征:挂着较多 MCP 工具、项目说明文档较长
(降低推理强度、新开对话可降低触发频率,但不能根除。)
## 期望
1. 桥接层的心跳应使用 Codex Responses 能识别的形式(或作为 SSE 注释 / 可忽略事件),不要注入客户端无法解析的 `keepalive` 事件
2. 不要因为单个 SSE 事件解析失败就判定整个流结束
3. 若确为网关侧心跳协议,请与 Codex 的 Responses SSE 解析保持一致
## 诊断信息位置
- 客户端日志:`%APPDATA%\Cindy\logs\main-*.log`
- Codex 侧日志:`%APPDATA%\Cindy\codex-home\logs_2.sqlite`
- 关键词:`stream closed before response.completed`、`Failed to parse SSE event`、`unhandled responses event: "keepalive"`、`Codex reconnect-stall`、`interrupted-turn auto-resume`
---
## 提交时的任务环境
仅代表提交时快照,不一定是故障环境。OS 来自提交客户端本机,不含 SSH 远端主机;Harness / 模型来自当前任务。与运行环境无关的反馈可忽略本段。
**版本区域**: CN
**OS**: win32 x64 (10.0.19045)
**Harness**: Pi
**Model ID**: ` deepseek/deepseek-v4-flash-vision-exp `
**界面语言**: zh-CN
Contributor guide
Research direction
Start by tracing the cindy_codex /responses streaming bridge and its SSE event handling. Use the client logs and Codex logs named in the issue to reproduce the keepalive and parse-failure sequence. Done means keepalive traffic no longer prematurely closes the stream, a turn reaches response.completed, and isolated event parsing does not abort the whole response.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100