makecindy / makecindy/cindy

fix(desktop): Codex 流断开后会话残留 active,输入与模型切换失效

Open
#2,051 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
2.7k
Forks
401
Avg merge
21h 48m
Merged PRs (30d)
776

Description

## 摘要

Cindy Desktop 0.1.36 在 Codex 会话的响应流被客户端断开后,未能把会话状态收口到可恢复状态。受影响会话会在侧边栏显示红点,但仍保留 `active` / 忙碌态;用户无法继续发送消息,也无法切换模型。

这不是单纯的模型服务失败:同一时间上游请求返回 200,但客户端流被断开,随后本地会话没有得到可操作的终态或恢复入口。

## 环境

- 客户端:Cindy Desktop 0.1.36
- 平台:macOS darwin arm64
- 会话标题:`PR Review Follow-up`
- sessionId:`1d28a9aa-7db7-4901-8515-ea9725788c81`
- agent:Codex
- model:`codex/gpt-5.6-sol`
- threadId:`019fd7c3-f176-7462-a16f-13a1690133fb`
- 工作目录:FiloMail 项目

## 复现路径

1. 在 Desktop 中创建或打开一个 Codex 会话,并让其执行较长的 PR review/follow-up 工作。
2. 在响应流进行期间触发客户端连接重建、device-link relay 重启、窗口/renderer 重新订阅,或其他会导致响应客户端断开的场景。
3. 等待会话出现红点或响应流停止。
4. 在同一会话中发送新消息,或尝试切换模型。

当前事故的可观测触发线索是 device-link 服务重启和 relay 504;需要补充确定性测试,覆盖纯本地 renderer/响应流断开场景。

## 实际行为

- 会话边上出现红点。
- 发消息没有可见响应,AI 不继续处理。
- 模型选择器不可切换或切换无效。
- 会话仍被 UI/状态层视为 active/busy。
- 后端可能仍产生 Codex 请求或输出,但客户端没有收到完整终态。
- 用户只能重启 Cindy,或切换到新会话才能恢复操作。

## 期望行为

响应客户端断开后,系统必须在有界时间内完成以下之一:

1. 重新连接并 rehydrate 原会话,恢复正常输入和模型切换;或
2. 将当前 turn 收口为明确、可操作的失败/中断状态,清除 active-turn、recovery、steering 和队列阻塞,并提供一次安全的重试入口。

红点应能解释具体原因;不能让会话永久停留在 active/busy,也不能要求用户重启整个应用才能继续。

## 日志证据

本机 `main-2026-08-07.log`:

- 18:26:42:`device-link disconnected (code=1012, reason=service restart)`
- 18:27:01:`device-link request timeout`,请求为 `local-db:sessions:list`
- 18:27:03:device-link API 返回 504
- 18:27:05:relay connection error,HTTP 504
- 18:27:17:`device-link:subscribe` 连续超时,目标设备 responsiveness circuit opened

本机 `agent-2026-08-07.ndjson`:

- 同一 Codex thread 的上游请求多次记录 `status: 200`。
- 随后反复出现:`client disconnected mid-response — aborting upstream request`。
- 这说明至少有一条路径是“上游请求正常返回/正在生成,但客户端响应订阅先断开”,而不是单纯的 401/403/429/500 模型错误。

## 初步根因判断

疑似是 Desktop Main、renderer 输入协调器和 Codex response stream 的生命周期没有在客户端断开边界统一收口:

- proxy 在客户端断开后会 abort upstream request;
- session/coordinator 没有可靠地产生对应的 terminal error/interrupted/recovery checkpoint;
- 持久化或内存中的 active-turn / queue / steering 状态可能残留;
- renderer 只收到红点或部分状态,却没有恢复输入和模型选择的状态广播。

需要重点确认:

1. `client disconnected mid-response` 到底由 renderer 重订阅、device-link 重连、窗口生命周期,还是 session stream listener 被替换触发。
2. 断开后是否进入了统一的 turn error/abort cleanup 路径。
3. `activeTurn`、`isTurnRunning`、recovery、pending queue、steering marker、`sessions.status` 和 UI projection 是否在同一 generation 下收口。
4. 当上游已经返回 200、但客户端断开时,是否存在“请求可能已经执行”的重复重试风险。

## 建议修复方向

- 将 response client disconnect 接入统一的 session terminal/recovery 协议,不允许只在 proxy 层 abort upstream 后静默结束。
- 为该路径持久化明确的失败/中断状态和可重试 checkpoint。
- 原子清理旧 turn 的 active/recovery/steering/queue 状态,同时使用 session generation/clientId 防止迟到事件清理新 turn。
- renderer 重新连接或 rehydrate 后,重新拉取 authoritative session/input projection,而不是依赖断连前的本地 running 快照。
- 仅对明确未接受的请求自动重试;对已经进入上游或结果未知的请求,保留幂等边界,避免重复执行 PR review 操作。
- 红点应关联可操作的错误原因和恢复动作,而不是成为唯一反馈。

## 风险

不能通过无条件清除 active 状态或无限重发消息来修复,否则可能在上游已经接受请求时造成重复执行、重复修改文件或重复发送 PR 评论。修复应优先保证 generation、clientId 和 accepted/unknown 状态边界正确。

## 相关记录

- Issue #2045:上游 403 后会话仍显示运行中,继续消息进入队列且无法正常派发;用户表现和 active-turn/队列失同步相近,但本事故日志显示上游请求也可能返回 200。
- Issue #1932:回合完成信号丢失后会话永久显示思考中,后续消息无法派发;属于相同的会话生命周期/状态收口问题族。
- PR #1642:Codex retry recovery stateful,最接近恢复路径,但未明确覆盖客户端 response stream 断开。
- PR #2041:queue-head recovery 解锁,覆盖相邻的 rehydrate/队列卡死路径。
- PR #1385:增加 device-link 断开原因可观测性,但明确不修 device-link 根因。
- PR #1643:不属于本问题,主要处理晚到 agent 终态与模型目录快照。

## 验收标准

- 新增 Desktop/Codex 回归测试,模拟上游正常返回或生成期间客户端 response stream disconnect。
- 断开后旧 turn 在有界时间内进入明确的 completed/failed/interrupted/recoverable 状态,不再永久 active/busy。
- 用户发送的新消息可以正常派发,或明确显示可重试/可取消状态。
- 模型选择器恢复可用;如果当前 turn 仍确实在执行,界面必须展示真实状态和可操作的停止/恢复入口。
- renderer/device-link rehydrate 后能从 Main 的 authoritative snapshot 恢复 session 和 input queue projection。
- 迟到的旧 response/event 不得清理或覆盖新的 turn。
- 对 response 已可能到达上游的请求,不发生无幂等保护的重复派发。
- 红点包含明确原因或可进入恢复动作,不再只有无解释的视觉标记。
- 覆盖普通本地 Desktop、device-link 重连、renderer 重订阅和应用重启后的恢复场景。

Contributor guide

Open the contributing guide

Research direction

No source files or tests are named. Trace the `client disconnected mid-response` path through Desktop Main, the renderer input coordinator, and the Codex response stream, then inspect the related recovery work in PRs #1642 and #2041. Add deterministic disconnect and rehydrate tests covering terminal cleanup, authoritative session projection, stale events, and retry safety.

Written by the indexing model from the issue text.

Assessment

Tech stack
electron, typescript
Domain
backend-api-design, desktop, testing-qa
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.