From 3e2932d30e06cb9784d99dcfd0796ce7d0e525d1 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 7 Nov 2026 13:53:54 -0800 Subject: [PATCH] fix(desktop): skip the Local Network hint when Sim Cloud is unreachable On macOS 15+ every ERR_ADDRESS_UNREACHABLE load failure was explained as a Local Network privacy block, including failures to reach Sim Cloud, which is public and never gated by that permission. classifyLoadError now takes the failed URL and keeps the hint for self-hosted servers only, including ones reached through a DNS name that resolves to a private address. --- apps/desktop/src/main/load-health.test.ts | 14 ++++++++++---- apps/desktop/src/main/load-health.ts | 7 +++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/main/load-health.test.ts b/apps/desktop/src/main/load-health.test.ts index e547daa4836..aac0ee58109 100644 --- a/apps/desktop/src/main/load-health.test.ts +++ b/apps/desktop/src/main/load-health.test.ts @@ -3,14 +3,20 @@ import { attachLoadHealth, classifyLoadError } from '@/main/load-health' import { BrowserWindow as MockBrowserWindow } from '@/test/electron-mock' describe('classifyLoadError', () => { + const SELF_HOSTED_URL = 'http://sim.corp.internal:3000/workspace' + it('does NOT ignore ERR_FAILED (-2) or ERR_IO_PENDING (-1)', () => { - expect(classifyLoadError(-2)).toBe('unreachable') - expect(classifyLoadError(-1)).toBe('unreachable') + expect(classifyLoadError(-2, SELF_HOSTED_URL)).toBe('unreachable') + expect(classifyLoadError(-1, SELF_HOSTED_URL)).toBe('unreachable') }) it('reports ERR_ADDRESS_UNREACHABLE as a local-network block only under Local Network privacy', () => { - expect(classifyLoadError(-109, true)).toBe('local_network') - expect(classifyLoadError(-109, false)).toBe('unreachable') + expect(classifyLoadError(-109, SELF_HOSTED_URL, true)).toBe('local_network') + expect(classifyLoadError(-109, SELF_HOSTED_URL, false)).toBe('unreachable') + }) + + it('never blames Local Network privacy for Sim Cloud, which is public', () => { + expect(classifyLoadError(-109, 'https://www.sim.ai/workspace', true)).toBe('unreachable') }) }) diff --git a/apps/desktop/src/main/load-health.ts b/apps/desktop/src/main/load-health.ts index 56c73d89f45..29019c2af2d 100644 --- a/apps/desktop/src/main/load-health.ts +++ b/apps/desktop/src/main/load-health.ts @@ -1,6 +1,7 @@ import { release } from 'node:os' import { createLogger } from '@sim/logger' import type { BrowserWindow } from 'electron' +import { isSimCloudOrigin } from '@/main/config' import { type EventRecorder, scrubUrl } from '@/main/observability' const logger = createLogger('DesktopLoadHealth') @@ -32,6 +33,7 @@ export type LoadErrorKind = */ export function classifyLoadError( errorCode: number, + url: string, localNetworkPrivacy: boolean = hasLocalNetworkPrivacy() ): LoadErrorKind { // Only ERR_ABORTED (-3) and success (0) are ignored. ERR_FAILED (-2) and @@ -54,7 +56,8 @@ export function classifyLoadError( // macOS Local Network privacy fails the socket with EHOSTUNREACH, which // Chromium reports as ERR_ADDRESS_UNREACHABLE (-109), so a self-hosted server // on a private address looks like a routing failure until Sim is allowed. - if (errorCode === -109 && localNetworkPrivacy) { + // Sim Cloud is public, so that permission can never be what blocked it. + if (errorCode === -109 && localNetworkPrivacy && !isSimCloudOrigin(url)) { return 'local_network' } return 'unreachable' @@ -151,7 +154,7 @@ export function attachLoadHealth(win: BrowserWindow, deps: LoadHealthDeps): Load return } clearTimeout(watchdogTimer) - const kind = classifyLoadError(errorCode) + const kind = classifyLoadError(errorCode, validatedURL) if (kind === 'ignored') { return }