makecindy / makecindy/cindy

Codex 对话的流式响应被 keepalive 帧打断,导致每轮几乎必然中断并自动续跑

Open
#4,229 1 comment 0 reactions 0 assignees View on GitHub
bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.