Skip to content

fix(desktop): 收口迟到 wake 终态泄漏的 pendingTaskWake 桥接,修复侧栏 spinner 永久转圈 - #2952

Merged
MagicLizi merged 4 commits into
makecindy:mainfrom
mengfanyu9410-hub:fix/pending-task-wake-bridge-leak
Aug 19, 2026
Merged

fix(desktop): 收口迟到 wake 终态泄漏的 pendingTaskWake 桥接,修复侧栏 spinner 永久转圈#2952
MagicLizi merged 4 commits into
makecindy:mainfrom
mengfanyu9410-hub:fix/pending-task-wake-bridge-leak

Conversation

@mengfanyu9410-hub

@mengfanyu9410-hub mengfanyu9410-hub commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

这次改了什么

摘要

修复侧栏 spinner 在会话完成后永久转圈:主轮 Done 之后才到达的 wake 型任务终态(fork 会话收到父会话任务的终态、重连重放等)会置位唤醒桥接 pendingTaskWake,但设计中的两条消费路径(wake turn 启动 / wake-fail Done)在这类迟到/误投场景永远不会发生,桥接又不在任何 reconcile 覆盖内 —— running 快照被永久撑住、无自愈路径(完整根因分析见 #2951)。本 PR 把桥接纳入既有的「活动熄灭延迟对账」机制:置位于主轮 Done 之后的桥接自动调度权威对账,四条件全部满足才收口(详见下文),否则重新调度复查。

变更类型

  • fix 缺陷修复
  • feat 新功能
  • refactor / perf 重构或性能优化
  • docs / test / chore 文档、测试或工程维护
  • 其他:

范围

UI 变化

不涉及:改动命中 renderer/lib 路径但无任何视觉/交互/文案变化 —— 修复的是 running 状态残留,spinner 组件、样式、布局均未触碰;修复前后差异仅为「泄漏场景下 spinner 是否永久转」这一状态行为,已由 store 级回归测试固化(泄漏复现 → 收口熄灭)。

  • 引用的设计规范:不涉及(无视觉/交互/文案变化,理由同上)。

怎么验证的

自动验证

pnpm --dir apps/desktop vitest run src/renderer/lib/__tests__/pendingTaskWakeBridgeReconcile.test.ts
结果:7 passed (7) —— 泄漏复现+收口 / 最小年龄闸 / 在飞代际一致性 / 多 wake 依次消费 /
权威表有活任务不误杀 / 旧签名路径不变 / 正常 wake 消费链路不受影响

pnpm --dir apps/desktop vitest run <sessionRowRenderIsolation / listSessionTasks /
  sidebarAttentionBadge / sessionService / makerQueueState>
结果:48 + 18 passed,全绿

pnpm --dir apps/desktop typecheck
结果:0 error

手工验证

不涉及:泄漏依赖「主轮 Done 后迟到终态」的时序,真机难以稳定手工触发;行为已由驱动真实 store 的回归测试覆盖(__applyStatusUpdateForTest / __applyStreamEventForTestgetRunningSnapshot 断言)。

未执行的验证

  • 未跑全仓测试套件(改动集中于单文件,已跑该文件全部关联测试);
  • mobile 不涉及(纯 desktop renderer 状态逻辑)。

风险

风险分类

  • 其他:running 快照收口时序(桥接被过早清零的竞态)

影响与回滚

  • 时序风险已按 bot review 意见双闸防护:①代际一致(在飞窗口内新增/被消费的桥接一律不收,重新调度复查);②最小年龄 10s(WAKE_BRIDGE_RECONCILE_MIN_AGE_MS,给慢机/高负载下合法 wake 留启动余量);另有主 turn 不在跑 + main 权威表无 wake 型任务两条件。极端情况下(合法 wake 超过 10s 才启动且期间对账恰好落地)桥接会被提前清零,后果限于 spinner 短暂熄灭 + 一次完成通知,wake turn 启动后 isRunning 立即重新撑起,非终局错误。
  • 默认路径零行为变化:reconcileWakeBridge 仅活动熄灭延迟对账路径传入;挂载/面板水合等旧签名调用不受影响(有测试固化);远程会话沿用既有 isRemoteSessionSticky 多阶段豁免。
  • 回滚:revert 本分支两个 commit 即可,无数据/协议/迁移影响。

主轮 Done 之后才到达的 wake 型任务终态(fork 会话收到父会话任务的终态、重连
重放等)会置位唤醒桥接,但设计中的两条消费路径(wake turn 启动 / wake-fail
Done)在这类迟到/误投场景永远不会发生;pendingTaskWake 又不在
reconcileStaleRunningTasks 的对账覆盖内,活动熄灭对账沿的 running 候选粗筛
(迟到终态本身是 completed,候选恒空)还会把它挡在自愈门外 —— running 快照
被永久撑住,侧栏 spinner 永转、无自愈路径。

修复(全部复用既有的活动熄灭延迟对账机制):
- agent_task_update:主轮 Done 已越过后置位的桥接,置位即调度一次延迟对账兜底;
- 对账粗筛(调度沿 + 到点回调):pendingTaskWake > 0 时放行,不再仅看 running 候选;
- seedBackgroundTaskSnapshots 新增 opts.reconcileWakeBridge:主 turn 不在跑且
  main 权威表无任何 wake 型任务时清零桥接三元组;权威表仍有 wake 任务在跑或
  wake turn 已启动(桥接已被消费)时不动,不误杀真实空窗。

回归:pendingTaskWakeBridgeReconcile.test.ts 4 例(泄漏复现+收口 / 权威表有
活任务不误杀 / 旧签名路径行为不变 / 正常 wake turn 消费链路不受影响)。
typecheck 通过;makerChatStore.ts 存量 lint 问题(9148 行 prefer-const,主干
同样报)未在本提交范围内。

Signed-off-by: mengfanyu9410-hub <282780248+mengfanyu9410-hub@users.noreply.github.com>
@mengfanyu9410-hub
mengfanyu9410-hub requested a review from a team as a code owner August 18, 2026 11:18

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6807d3f784

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/desktop/src/renderer/lib/makerChatStore.ts Outdated
@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown

Greptile Summary

本 PR 为迟到的 wake 终态补充权威对账,在确认不存在待执行 continuation 后收口泄漏的 pendingTaskWake,避免侧栏 spinner 永久转圈。

  • IPC 快照新增 pending continuation 数量
  • renderer 对账加入计数、单调代次、最小年龄及主进程权威状态校验
  • 新增覆盖旧快照、计数 ABA、多 wake 和 continuation 空窗的回归测试

Confidence Score: 5/5

当前修改看起来可以安全合并。

未发现仍然存在的阻塞性故障;此前报告的旧快照清除新桥接和计数 ABA 问题均已由计数与单调代次校验修复。

Important Files Changed

Filename Overview
apps/desktop/src/renderer/lib/makerChatStore.ts 为 wake 桥接增加单调代次、年龄闸和基于权威快照的延迟收口,当前 HEAD 已覆盖此前报告的旧快照与计数 ABA 场景。
apps/desktop/src/main/maker-ipc/register.ts 后台任务快照响应新增 pending continuation 数量,供 renderer 判断是否可以安全收口桥接。
packages/maker-core/src/agents/claude-code/index.ts 从 continuation claims 统计 awaiting 与 active 状态,为 IPC 提供权威计数。
packages/maker-core/src/session.ts Session 层公开 continuation 计数并为关闭或不支持该能力的会话提供零值回退。
apps/desktop/src/renderer/lib/tests/pendingTaskWakeBridgeReconcile.test.ts 覆盖泄漏收口、最小年龄、权威 continuation、在飞新增、计数 ABA 和多 wake 消费等关键时序。

Sequence Diagram

sequenceDiagram
  participant Agent as Claude Code Agent
  participant Main as Electron Main
  participant Renderer as makerChatStore
  participant UI as Sidebar
  Agent-->>Renderer: 迟到的 wake 终态
  Renderer->>Renderer: pendingTaskWake++, gen++
  Renderer-->>UI: 保持 running
  Renderer->>Main: listSessionBackgroundTasks
  Main->>Agent: 查询任务与 continuation
  Agent-->>Main: tasks + pendingContinuations
  Main-->>Renderer: 权威快照
  alt 代次稳定且无任务/continuation并已超龄
    Renderer->>Renderer: 清零 pendingTaskWake
    Renderer-->>UI: spinner 熄灭
  else 仍可能存在合法 wake
    Renderer->>Renderer: 保留桥接并重新调度
  end
Loading

Reviews (3): Last reviewed commit: "test(desktop): 对账粗筛用例适配桥接放行语义(CI 失败根因)" | Re-trigger Greptile

Comment thread apps/desktop/src/renderer/lib/makerChatStore.ts Outdated
@MagicLizi MagicLizi added awaiting-discussion 等待维护者讨论(review-pr) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) labels Aug 18, 2026

@MagicLizi MagicLizi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

格式门未通过

以下问题需要修正后才能进入代码审查:

  1. PR Description 缺段落:请补充"这次改了什么 / 怎么验证的 / 风险"相关说明,参照 .github/PULL_REQUEST_TEMPLATE.md 模板。

修正后推送新 commit 即可,下轮自动重新审查。

@MagicLizi

Copy link
Copy Markdown
Contributor

💡 本 PR 命中 UI 路径(apps/desktop/src/renderer/lib/makerChatStore.ts)但 description 未附界面效果证据——建议补充改动后效果:截图/录屏,或改动后界面的 HTML 页面(```html 代码块、.html 附件或在线预览链接),便于确认界面符合 DESIGN.md 设计规范。

@MagicLizi MagicLizi added status:awaiting-author 等作者修改(review-pr 自动维护,仅展示) and removed awaiting-discussion 等待维护者讨论(review-pr) labels Aug 18, 2026
review 意见(greptile Issue 1 / dash-s-cindy 场景 2-4)共同点:旧空快照可能
清除请求在飞期间新置位的桥接;「main 运行表为空」也不能独立证明合法 wake
不会稍后启动;多 wake 场景不能被一次清空。

改动:
- state 新增 pendingTaskWakeArmedAt(最近置位时刻,置位刷新、清零置 null);
- 对账回调在发起快照请求前捕获桥接代际(capturePendingWakeBridgeCount);
- seedBackgroundTaskSnapshots 的 reconcileWakeBridge 由 boolean 改为代际计数,
  收口四条件:计数与代际一致(在飞无新增无消费)+ 主 turn 不在跑 + 距最近
  置位超过 WAKE_BRIDGE_RECONCILE_MIN_AGE_MS(10s)+ 权威表无 wake 型任务;
  条件未齐且计数仍 >0 时重新调度下一轮对账复查,不做部分清除;
- 回归测试 4→7 例:新增在飞代际、最小年龄闸、多 wake 依次消费三例;
  同步补齐 makerQueueState.test.ts 状态构造器的新字段。

Signed-off-by: mengfanyu9410-hub <282780248+mengfanyu9410-hub@users.noreply.github.com>
@mengfanyu9410-hub

Copy link
Copy Markdown
Contributor Author

已按 review 意见推送 ea8ebc4,逐条回应:

@greptile-apps Issue 1(旧快照清除新桥接) — 已修。对账回调在发起 listSessionBackgroundTasks 之前捕获桥接代际(capturePendingWakeBridgeCount),reconcileWakeBridge 由 boolean 改为代际计数;收口要求「响应落地时计数与代际完全一致」—— 在飞窗口内新置位或被 isTurnStart 消费过的桥接一律不收,本轮改为重新调度复查。附加最小年龄闸(WAKE_BRIDGE_RECONCILE_MIN_AGE_MS = 10s):距最近置位不足 10s 一律不收口,给慢机/高负载下合法 wake turn 留启动余量。新增回归:请求在飞窗口内新置位的桥接不被旧快照清除(代际一致性) / 距最近置位不足最小年龄时不收口 / 多 wake 依次消费:对账不一次清空尚待消费的合法计数

格式门(PR Description 缺段落) — 已按 .github/PULL_REQUEST_TEMPLATE.md 重写 description(改了什么 / 怎么验证的 / 风险与回滚)。

UI 效果证据 — 本 PR 命中 renderer/lib 路径但无任何视觉/交互/文案变化:未触碰 spinner 组件、样式或布局,修复的是 running 状态残留(泄漏场景下 spinner 永久转 → 置位后 ~10-13s 内熄灭;正常链路行为不变)。修复前后差异为纯状态行为,已由驱动真实 store 的 7 例回归测试固化(泄漏复现 → 收口熄灭断言),无法用静态截图有效呈现,故按模板口径填写「不涉及 + 理由」。如维护者仍需要,可补 store 状态时间线的文字 trace。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ea8ebc4254

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/desktop/src/renderer/lib/makerChatStore.ts
@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown

LGTM. The implementation correctly addresses all three concerns from the original review:

Generation consistency (Issue 1): capturePendingWakeBridgeCount is snapshotted before the listSessionBackgroundTasks IPC, and bridgeSettled = next.pendingTaskWake === capturedWakeBridge correctly rejects any reconcile where the count shifted in-flight — covering both new arms and isTurnStart consumptions mid-flight.

Minimum age gate: pendingTaskWakeArmedAt is updated only on real arm events (wakesAfterTerminal ? Date.now() : state.pendingTaskWakeArmedAt), and the 10s constant gives a generous margin. The else-branch re-scheduling from within setState is fine since it's a deferred setTimeout, not a re-entrant state mutation.

Early-return bypass: The two guard sites — scheduleBackgroundTaskReconcile and initGlobalListeners — are now correctly both-gated (size === 0 && capturedWakeBridgeCount === 0), so wake-bridge-only leaks aren't silently dropped before the IPC is even issued.

The forceFinalizeOnSessionClosed clearing pendingTaskWakeArmedAt: null is a good defensive cleanup. The 7 regression tests cover the meaningful boundary conditions (generation race, age gate, multi-wake partial consume, authority-table-alive guard, legacy path unchanged).

No outstanding concerns. The fix is well-scoped and the fallback behavior (worst case: spinner clears 10s late, then isTurnStart re-arms it if a legitimate wake turn actually arrives) is acceptable. Ready to merge.

Comment thread apps/desktop/src/renderer/lib/makerChatStore.ts Outdated
@MagicLizi MagicLizi added awaiting-discussion 等待维护者讨论(review-pr) and removed status:awaiting-author 等作者修改(review-pr 自动维护,仅展示) labels Aug 18, 2026
@MagicLizi

Copy link
Copy Markdown
Contributor

@mengfanyu9410-hub 👋 这个 PR 还有 3 条 review conversation 没 resolve(apps/desktop/src/renderer/lib/makerChatStore.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

@MagicLizi

Copy link
Copy Markdown
Contributor

⏸️ 本 PR 触发维护者确认门 (product: 命中 UI 路径)。

已开启讨论 issue,等待维护者在 PR 上 Approve 后自动放行。

维护者: @dashhuang @DavidShenXD @fmfsaisai @kirozeng @MagicLizi @xdanger @zqchris

讨论 issue:#2982

回应第二轮 review(codex P2 两条 / greptile P1):

- codex P2:noteBackgroundTaskEvent 在任务终态立即将其移出
  runningBackgroundTasks,listBackgroundTasks() 的空快照不能证明没有待启动
  的 continuation。新增贯通链路:claude-code agent
  countPendingWakeContinuations()(awaiting+active claim 数,cancelled 不计)
  → base-agent 接口声明 → Session 透传 → LIST_SESSION_BACKGROUND_TASKS IPC
  返回 pendingContinuations → renderer 收口第 6 条件:计数**明确为 0** 才允许
  清桥接;信号缺失(旧 main)按不可用处理,只重试、不退化回空表判据。
- greptile P1(ABA):state 新增 pendingTaskWakeGen(仅置位自增),对账捕获
  与收口比对代次 —— 在飞窗口内「消费后又置位、计数恰好回到捕获值」不再可能
  被误认为同一代际。
- 回归测试 7→10 例:新增 continuation>0 不收口 / 信号缺失不收口 / ABA 代次
  三例;makerQueueState 构造器补 pendingTaskWakeGen。

验证:新测试 10/10、makerQueueState 18/18、周边 48 例全绿;desktop typecheck
0 错;maker-core 直跑 tsc 与主干错误集完全一致(16 个,全为 codex/pi 测试
存量,本次改动的三个文件零新增)。

Signed-off-by: mengfanyu9410-hub <282780248+mengfanyu9410-hub@users.noreply.github.com>
@mengfanyu9410-hub

Copy link
Copy Markdown
Contributor Author

已推送 5ee8cc7,回应第二轮 review 的三条 thread:

Codex P2 ×2(空运行任务表 ≠ 无待启动的 continuation) — 接受,按建议改为以 provider continuation claim 为权威判据。新增贯通链路:claude-code agent countPendingWakeContinuations()(awaiting + active claim 数,cancelled 不计)→ base-agent 接口声明 → Session 透传 → LIST_SESSION_BACKGROUND_TASKS IPC 返回 pendingContinuations → renderer 收口新增决定性第 6 条件:该计数明确为 0 才允许清桥接。任务终态后仍 awaiting 的 claim(即使超过 10s)会持续挡住收口,直到 wake turn 启动(claim → active → settled 释放,期间 isRunning 亦为 true)或被取消。信号缺失(旧 main,字段 undefined)按「不可用」处理 —— 只重试、绝不退化回空表判据。回归:main 权威 continuation 计数 > 0 时不收口 / continuation 信号缺失(null)时不收口

Greptile P1(计数代际 ABA 碰撞) — 接受。state 新增 pendingTaskWakeGen(仅置位自增的单调代次),对账在请求发起前捕获 {count, gen},收口要求两者与当前值完全一致 —— 「在飞窗口内消费后又置位、计数恰好回到捕获值」时代次必然已变,旧响应无法冒充同一代际。回归:ABA 碰撞:计数回到捕获值但代次不同,不收口

验证:新回归 10/10;makerQueueState 18/18、周边 48 例全绿;desktop typecheck 0 错;maker-core 直跑 tsc --noEmit 与 main 分支错误集完全一致(16 个,全为 codex/pi 测试文件存量,本次触碰的 claude-code/index.ts / base-agent.ts / session.ts 零新增)。

三条 thread 将随本评论一并 resolve;如仍有疑虑欢迎 reopen。

CI「Linux unit tests (2/2)」失败根因:既有用例『远程会话豁免;无 running 条目
不发 IPC』的第二段用 wake 型 completed 终态构造「无 running 条目」场景 —— 该帧
在本 PR 语义下会置位唤醒桥接并放行对账 IPC,而这正是修复目的(旧断言把泄漏
永转路径固化成了预期)。

调整:①原段改用 local_bash 终态(白名单外不置位),保留「没活不空跑 IPC」的
原验证意图;②追加 wake 终态段,固化「桥接置位 → 粗筛放行对账」的新语义,并
指向 pendingTaskWakeBridgeReconcile.test.ts 的收口条件测试。
本地实跑:该文件 11/11、桥接回归 10/10 全绿。

Signed-off-by: mengfanyu9410-hub <282780248+mengfanyu9410-hub@users.noreply.github.com>
@mengfanyu9410-hub

Copy link
Copy Markdown
Contributor Author

CI 补充说明:昨日「Linux/Windows unit tests (2/2)」的真实失败点是既有用例 makerChatStoreBackgroundTaskReconcile.test.ts › 远程会话豁免;无 running 条目不发 IPC —— 其第二段恰好用 wake 型 completed 终态构造「无 running 条目」场景,该帧在本 PR 语义下会置位唤醒桥接并放行对账 IPC(这正是修复目的:旧断言把「迟到终态 → 永转且不对账」的泄漏路径固化成了预期)。已在 1a0c89c 调整:原段改用 local_bash 终态保留「没活不空跑 IPC」的原意图,另追加 wake 终态段固化「桥接置位 → 放行对账」新语义。本地该文件 11/11 全绿,等本轮 CI 复核。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a0c89ccad

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/desktop/src/renderer/lib/makerChatStore.ts
@MagicLizi

Copy link
Copy Markdown
Contributor

@mengfanyu9410-hub 👋 这个 PR 还有 1 条 review conversation 没 resolve(apps/desktop/src/renderer/lib/makerChatStore.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

@MagicLizi MagicLizi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review passed. Solid fix for the spinner-stuck bug with well-designed reconciliation logic and thorough test coverage. The six-condition gate prevents false clearance effectively.

@MagicLizi MagicLizi removed the awaiting-discussion 等待维护者讨论(review-pr) label Aug 19, 2026
@MagicLizi
MagicLizi merged commit 9bd7e63 into makecindy:main Aug 19, 2026
11 checks passed
@MagicLizi

Copy link
Copy Markdown
Contributor

修得漂亮 — 六条件对账门(代际计数+生存期+authority continuation=0)把这种隐蔽的状态泄漏收得很干净,10 条新测试把每个守卫条件都单独验了一遍。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

侧栏 spinner 在会话完成后永久转圈:迟到的 wake 任务终态泄漏 pendingTaskWake 桥接,无自愈路径

2 participants