diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts index 24de482159..6779b7ac93 100644 --- a/apps/desktop/electron/main.ts +++ b/apps/desktop/electron/main.ts @@ -13186,7 +13186,11 @@ function runPrimaryRecoverySpawn(code: number | null, signal: string | null) { // Terminal boot failures still own their existing recovery UI. Only a // supervisor-owned respawn that failed transiently before ready may spend // another bounded recovery slot. - if (bootstrapFailure || backendStartFailure || remoteReauthFailure) { + const latched = latchedBootFailure() + + if (latched) { + rememberLog(`[supervisor] respawn refused: boot failure latched: ${firstLine(latched.message)}`) + return } @@ -13232,6 +13236,17 @@ function scheduleUnexpectedPrimaryRecovery({ return true } +/** + * The terminal boot failure currently latched in this process, if any. These + * latches are cleared only by an explicit recovery path (reset, repair, + * apply-config, confirmed sign-in, or the child 'exit' handler), never by a + * retry, so both the per-request short-circuit in runHermesStart and the + * supervisor's respawn refusal must consult the same trio in the same order. + */ +function latchedBootFailure(): Error | null { + return bootstrapFailure ?? backendStartFailure ?? remoteReauthFailure ?? null +} + async function runHermesStart({ supervisorRecovery = false }: { supervisorRecovery?: boolean } = {}) { // Only the single-instance lock holder may reap/spawn/claim the desktop // backend. A lock-losing instance must stay inert even if some path reaches @@ -13253,22 +13268,19 @@ async function runHermesStart({ supervisorRecovery = false }: { supervisorRecove // ensureGatewayOpen retries (and any other getConnection callers) from // restarting a 5-10 minute install loop while the user is still reading // the failure overlay. - if (bootstrapFailure) { - rememberLog(`[boot] bootstrap failure latched; refusing restart: ${firstLine(bootstrapFailure.message)}`) - throw bootstrapFailure - } + // + // A confirmed remote reauth rejection is likewise terminal until the user + // signs in. Short-circuiting here keeps the boot-failure overlay latched and + // its "Sign in" button clickable, instead of re-driving boot on every retry. + // + // Deliberately silent: this runs on every proxied request while a failure is + // latched (ensureBackend -> startHermes), so a log line here would flood the + // bounded rememberLog ring and evict the lines that explain the original + // failure. The supervisor logs the refusal once in runPrimaryRecoverySpawn. + const latched = latchedBootFailure() - if (backendStartFailure) { - rememberLog(`[boot] backend start failure latched; refusing restart: ${firstLine(backendStartFailure.message)}`) - throw backendStartFailure - } - - // A confirmed remote reauth rejection is terminal until the user signs in. - // Short-circuiting here keeps the boot-failure overlay latched and its - // "Sign in" button clickable, instead of re-driving boot on every retry. - if (remoteReauthFailure) { - rememberLog(`[boot] remote reauth failure latched; refusing restart: ${firstLine(remoteReauthFailure.message)}`) - throw remoteReauthFailure + if (latched) { + throw latched } // E2E: simulate a boot failure without breaking the real backend. The boot