fix(desktop): forget a Host-removed Session registration on reconnect restore - #4764
Conversation
… restore When the Runtime Host no longer serves a previously observed Session (Host restart with ephemeral state, Session GC, or deletion by another client), subscription.open deterministically answers not_found during attach. The observation registry only cleaned up pending registrations on restore failure, so a stale active registration failed every candidate start with "Failed to restore Session observations" and the reconnect lifecycle retried forever while renderer IPC surfaced RuntimeHostHandlerUnavailableError. Treat subscription.open/not_found as terminal in attach(): forget the registration regardless of lifecycle and notify the candidate so it can emit a deleted sessions-changed event; the renderer already retires the Session view safely. Compute failed restores from the registrations that survive attach so forgotten Sessions no longer fail the candidate. Fixes apache#4758
jackwener
left a comment
There was a problem hiding this comment.
Approved. I found no blocking issues.
I verified the production reconnect path: a surviving observation is replayed through subscription.open, and a Host not_found response previously left the active registration behind so every replacement candidate failed again. This patch narrows the terminal case to that exact operation and error code, removes the stale registration, emits the existing deleted session change, and computes restore failures only from registrations that still exist. Other restore failures remain retryable.
The new regression fails when the missing-Session classification is removed and passes with the fix. The affected Desktop suites pass 147/147. I also built the current-main synthetic merge and reran the candidate suite there (22/22); the merge is clean.
Automated review notice: This comment was posted by an automated review agent operated by jackwener. It is not an independent human review and does not replace one.
Problem
Fixes #4758.
When the desktop main process reconnects to the Runtime Host after an IPC drop,
desktop-managerhands the surviving observation registry (target.observations) to each replacement candidate, whoseattach()replays every registration throughsubscription.open. If a previously observed Session no longer exists on the Host (Host restart with ephemeral state, Session GC, deletion by another client), the server deterministically answerssubscription.open/not_found("Session was not found").The registry's
attach()failure cleanup only deleted pending registrations, so a stale active registration stayed in the map forever. The candidate then computedfailedSessionIds = observedSessionIds − restoredSessionIdsfrom the pre-attach snapshot, threwFailed to restore Session observations: <id>, and aborted the candidate start. Because the reconnect lifecycle only stops onRuntimeHostPermanentReconnectError, this plain error just fed the backoff loop — every new candidate failed on the same stale registration, and renderer IPC surfacedRuntimeHostHandlerUnavailableErrorthe whole time.Fix
runtime-host-session-observation-registry.ts: recognizeRuntimeHostOperationErrorwithoperation === "subscription.open"andcode === "not_found"as "the Host no longer serves this Session" (isMissingRuntimeHostSessionError). Inattach()'s catch path, forget the registration regardless of lifecycle and report it through a new optionalonSessionMissingcallback. All other failures keep the existing behavior (pending-only cleanup + error reporting), so genuinely transient restore failures still fail the candidate and get retried — see the existingretries candidate startup when a restored observation cannot seedtest, which still passes unchanged.runtime-host-desktop-candidate.ts: passonSessionMissingto emit adeletedsessions-changed event (the renderer already handlesdeletedsafely by refreshing the catalog and retiring the Session view), and computefailedSessionIdsfrom the registrations that survive attach so forgotten Sessions no longer fail the candidate start.This mirrors the deliberate asymmetry in
isRecoverableSubscriptionFailure:session.transcript.page/not_foundis transient and retryable, whilesubscription.open/not_foundat attach time is terminal.Tests
forgets an observed Session the Host no longer serves instead of blocking every reconnect: the second candidate's Host answerssubscription.open/not_foundfor the previously observed Session; the candidate starts (no throw), the registry forgets the stale entry, adeletedchange is emitted, and a later candidate can observe new Sessions on the same registry. The test fails without the fix.npm run build:mainclean; the 6 runtime-host desktop suites pass 132/132. (Full main-process suite: 2085/2090; the 5 failures are pre-existingbrowser-message-boxenvironment failures on a display-less Linux box, reproduced on pristineorigin/main.)Real-environment follow-up (not done here): restart a Host / expire a Session with the desktop running and confirm the
Failed to restore Session observationsloop no longer appears.