From 36e2228a4cfdc837557bafeded023984eb5c5e81 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sat, 25 Jul 2026 21:52:45 -0500 Subject: [PATCH] fix(desktop): latch a confirmed remote reauth failure so the overlay stays clickable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit shouldLatchBackendStartFailure deliberately never latches a remote failure: remote faults are usually transient and must stay retryable without an app restart. A confirmed reauth rejection is the exception — it cannot self-heal, because nothing changes until the user signs in again. Worse, not latching actively prevents the recovery it was protecting. A non-latching remote boot failure re-runs startHermes on every getConnection/api call, re-emits running: true, and the boot-failure overlay (visible = Boolean(boot.error) && !boot.running) hides itself — so the "Sign in" button flickers out from under the user before it can be clicked. Add shouldLatchRemoteReauthFailure as a separate predicate rather than changing the existing one, so transient remote failures keep self-healing. The two latches are complementary and never fire for the same failure. --- .../electron/backend-start-failure.test.ts | 31 ++++++++++++++++++- .../desktop/electron/backend-start-failure.ts | 31 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/apps/desktop/electron/backend-start-failure.test.ts b/apps/desktop/electron/backend-start-failure.test.ts index 0888d65fbc41..36d352d66cc2 100644 --- a/apps/desktop/electron/backend-start-failure.test.ts +++ b/apps/desktop/electron/backend-start-failure.test.ts @@ -2,7 +2,7 @@ import assert from 'node:assert/strict' import { test } from 'vitest' -import { shouldLatchBackendStartFailure } from './backend-start-failure' +import { shouldLatchBackendStartFailure, shouldLatchRemoteReauthFailure } from './backend-start-failure' test('latches a LOCAL backend failure so the install-retry loop is broken', () => { assert.equal(shouldLatchBackendStartFailure({ attemptedRemote: false }), true) @@ -21,3 +21,32 @@ test('the two branches are mutually exclusive (a failure either latches or stays assert.equal(latched, !attemptedRemote) } }) + +test('latches a CONFIRMED remote reauth failure so the overlay stays clickable', () => { + // Without this the non-latching remote path re-runs startHermes on every + // getConnection/api call, re-emits running:true, and the overlay hides + // itself — the "Sign in" button flickers away before it can be clicked. + assert.equal(shouldLatchRemoteReauthFailure({ attemptedRemote: true, isReauth: true }), true) +}) + +test('does not latch a transient remote failure as reauth', () => { + // A mint timeout or a host unreachable across sleep must still self-heal. + assert.equal(shouldLatchRemoteReauthFailure({ attemptedRemote: true, isReauth: false }), false) +}) + +test('never latches a LOCAL failure as reauth (that is backendStartFailure job)', () => { + assert.equal(shouldLatchRemoteReauthFailure({ attemptedRemote: false, isReauth: true }), false) + assert.equal(shouldLatchRemoteReauthFailure({ attemptedRemote: false, isReauth: false }), false) +}) + +test('the two latches never fire for the same failure', () => { + // They are complementary, not overlapping: local failures latch via + // backendStartFailure, confirmed remote reauth latches via its own flag. + for (const attemptedRemote of [true, false]) { + for (const isReauth of [true, false]) { + const start = shouldLatchBackendStartFailure({ attemptedRemote }) + const reauth = shouldLatchRemoteReauthFailure({ attemptedRemote, isReauth }) + assert.ok(!(start && reauth), `both latched for remote=${attemptedRemote} reauth=${isReauth}`) + } + } +}) diff --git a/apps/desktop/electron/backend-start-failure.ts b/apps/desktop/electron/backend-start-failure.ts index 4998b0164a70..3c5ffdbcad91 100644 --- a/apps/desktop/electron/backend-start-failure.ts +++ b/apps/desktop/electron/backend-start-failure.ts @@ -39,3 +39,34 @@ export interface BackendStartFailureContext { export function shouldLatchBackendStartFailure(context: BackendStartFailureContext): boolean { return !context.attemptedRemote } + +export interface RemoteReauthFailureContext { + /** True when the boot that just failed was dialing a REMOTE (or cloud) backend. */ + attemptedRemote: boolean + /** + * True when the failure was a CONFIRMED auth rejection (a credentialed + * probe got 401/403), not a transient connectivity fault. + */ + isReauth: boolean +} + +/** + * Whether a failed remote boot should latch as a reauth failure. + * + * This is the deliberate counterpart to `shouldLatchBackendStartFailure`, + * which never latches a remote failure because remote faults are usually + * transient and must stay retryable. A *confirmed* reauth rejection is the + * exception: it cannot self-heal, because nothing will change until the user + * signs in again. + * + * Without a latch, the non-latching remote path actively prevents recovery. + * Every subsequent `getConnection`/`api` call re-runs `startHermes`, re-emits + * `running: true`, and the boot-failure overlay (`visible = Boolean(boot.error) + * && !boot.running`) hides itself — so the "Sign in" button flickers out from + * under the user before they can click it. Latching holds the overlay still + * and clickable. Cleared on every recovery path (reset, repair, apply-config, + * and a confirmed sign-in) so a fresh session boots normally. + */ +export function shouldLatchRemoteReauthFailure(context: RemoteReauthFailureContext): boolean { + return context.attemptedRemote && context.isReauth +}