Repository navigation
fix(desktop): reconnect the IPC push relay with backoff - #1773
Merged
Merged
Conversation
A half-open socket answers nothing and never fires close, so every recovery mechanism (reconnect, hydration, subscription replay) waited forever while the UI showed stale streaming state. A 30s interval now issues the existing app.version RPC with a 10s deadline; an unanswered probe closes the socket locally and the established onclose ladder owns recovery. agent.send gains a 20s timeout so a composer submit cannot park forever on a dead socket. The wedged-socket test now answers the liveness probe, since a socket that ignores it is exactly what the watchdog closes.
The reducer now writes routing.executionId onto the stored turn so clients can correlate a canonical turn to the local runtime without threading the event envelope through state. A payload-supplied executionId that disagrees with routing is rejected as a routing conflict.
Composer, project tree, and running-thread indicators now follow the canonical replica once a turn correlates by execution identity, so a dropped legacy terminal event can no longer strand runningThreadIds. The optimistic window before turnStarted and the finalizing phase stay on the legacy path until a canonical turn proves correlation. The narrative projection keeps its existing child-only lifecycle gate.
The relay opened one socket and never recovered: a dropped connection silently degraded the renderer to WebSocket-only delivery for the rest of the session. Reconnect in the main process with bounded exponential backoff, reset on successful connect, and stop when the window dies or the relay is torn down. The renderer re-suppresses push channels as frames resume, so no reconnect handshake is needed.
No caller remains: consumers read runningThreadIds directly.
…hase The runtime reconcile stamps runtimePhase from a correlated canonical turn but never correlated the record's execution identity, so a canonically owned "running" phase made getCanonicalLifecycleTurn treat the provider-owned child turn as a local execution and suppress its projection. A later terminal canonical event could then no longer clear the stamped phase either. reconcileCanonicalRuntime now stamps turnExecutionId from the claiming turn's executionId on live claims, and the lifecycle gate only treats runtimePhase as locally owned when the canonical runtime turn is not the latest turn.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The Electron main process owns a
net.Socketto the server's IPC push endpoint and relays frames to the renderer. Previously it opened one socket and never recovered: a dropped connection sentipc-push-disconnectonce, the renderer un-suppressed channels and fell back to WebSocket, and the fast path stayed dead for the rest of the session.The relay now reconnects in the main process with exponential backoff (500 ms doubling to a 15 s ceiling), resets the delay on a successful connect, and stops reconnecting when the window is destroyed or the relay is torn down. No reconnect handshake is needed: the renderer re-suppresses push channels as frames resume, and the WebSocket path covers the gap.
Also removes
isThreadRunningfrom the thread store. The selector has no callers; consumers readrunningThreadIdsdirectly.Verified with the ipc-relay suite (19 tests), including new coverage for backoff doubling, reset on connect, and no reconnect after cleanup or window destruction.
tsc --noEmitis clean inapps/desktopandapps/web.Why
Part of the stale-streaming reliability program in
docs/plans/canonical-runtime-state-migration.md. A dead IPC relay silently degraded desktop push delivery; combined with the reactive-only WebSocket recovery ladder, silent transport death was the class of failure that left threads stuck showing streaming.UI Changes
None.
Config Changes
None.
Review Notes
Stacked on #1772. The reconnect backoff bounds (500 ms, 15 s ceiling) are new constants in
ipc-relay.ts.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.