Skip to content

Fix stale background response state after renderer recovery - #115

Open
sambitcreate wants to merge 1 commit into
mainfrom
feature/fix-detached-generation-reconciliation
Open

Fix stale background response state after renderer recovery#115
sambitcreate wants to merge 1 commit into
mainfrom
feature/fix-detached-generation-reconciliation

Conversation

@sambitcreate

Copy link
Copy Markdown
Owner

Summary

  • reconcile retained detached generations from main-process settlement and activity events
  • refresh the exact transcript and chat list before clearing renderer ownership
  • require a fresh authoritative inactive snapshot and clear only captured stream IDs, preserving genuinely active or newer work
  • recover the same state during root bootstrap when a terminal event was missed during a route error

Production incident

A completed chat could remain stuck on “Response continues in the background…” with a sidebar spinner after a renderer route error. Main had durably persisted and settled the generation, but the renderer retained its detached lifecycle owner after missing the one-shot terminal payload.

Verification

  • npx tsx --test renderer/lib/chat-terminal-sync.test.ts renderer/main/chat-transition.test.tsx
  • npm run type-check
  • npm run lint
  • npm test
  • npm run build

The regressions cover missed terminal delivery, genuine active work, same-chat newer-generation races, workspace isolation, and root recovery subscription ordering.

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes — initial review of the detached-generation recovery fix at 6bfd04c4, verified with the focused and adjacent suites plus npm run type-check and lint.

  • renderer/lib/chat-terminal-sync.ts — adds DetachedLifecycleChatReconciliation, pendingDetachedLifecycleChats(), captureDetachedLifecycleChat() (exact stream-id capture at event time), and clearInactiveDetachedLifecycleChat(), which removes only the captured ids from the detached/fallback/projection maps and re-validates chat+workspace against the current owner before deleting.
  • renderer/main/root-view.tsxreconcileDetachedLifecycleChat() waits for bounded main idle, refetches the exact transcript and chat list, then reads a fresh activity snapshot and clears only when the chat is inactive; the chats:settled handler now routes through it, and a new effect subscribes to chats:activity-changed before its one-shot bootstrap snapshot so a settle transition cannot fall between the two.
  • renderer/lib/chat-terminal-sync.test.ts / renderer/main/chat-transition.test.tsx — behavioral coverage for inactive repair, active preservation, same-chat newer-generation exactness, and workspace isolation, plus a source-contract test pinning the subscribe-before-snapshot ordering and the clear-after-fresh-snapshot wiring.
  • .papercuts/troubleshooting.md — investigation notes for the incident.

Two independent deep passes (mine plus two focused subagent traces) found no path where the new clear deletes still-running work — waitForChatIdle gates on generation ownership, the clear uses a snapshot taken after the refetch, and newer same-chat streams are excluded by capture — and no app-reachable path where a retained owner survives indefinitely. The residual best-effort property (a failed bootstrap snapshot read with no later activity event) is already documented in the code comments.

Pullfrog  | View workflow run | Using DeepSeek Flash𝕏

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant