diff --git a/apps/desktop/electron/connection-registry.test.ts b/apps/desktop/electron/connection-registry.test.ts index 705ce0a0aa..beb1ce7016 100644 --- a/apps/desktop/electron/connection-registry.test.ts +++ b/apps/desktop/electron/connection-registry.test.ts @@ -745,18 +745,21 @@ test('registry local route: per-profile override wins when global remote is also assert.deepEqual(route, { delegate: true, poolKey: 'research' }) }) -test('registry local route: a concrete remote-only profile is refused on the forced-local branch, including default', () => { +test('registry local route: a concrete remote-only profile is refused on the forced-local branch', () => { // globalRemote still force-locals a profile that exists on this machine - // (This device must not dial the remote). A profile that exists only on the - // remote must not spawn a local child — including default, which the - // ordinary existence guard exempts and would otherwise start silently. + // (This device must not dial the remote). A named profile that exists only + // on the remote must not spawn a local child. const named = resolveRegistryLocalRoute('inbox', { globalRemote: true, localProfileExists: false }) - const fallback = resolveRegistryLocalRoute('default', { globalRemote: true, localProfileExists: false }) assert.match(String(named.refuse ?? ''), /Profile "inbox" no longer exists/) assert.equal(named.delegate, false) - assert.match(String(fallback.refuse ?? ''), /Profile "default" no longer exists/) - assert.equal(fallback.delegate, false) + + // default is $HERMES_HOME, not profiles/default: a profiles/default probe + // reports absent, but This device -> default must still open locally. + assert.deepEqual(resolveRegistryLocalRoute('default', { globalRemote: true, localProfileExists: false }), { + delegate: false, + poolKey: 'conn:local::default' + }) const present = resolveRegistryLocalRoute('research', { globalRemote: true, localProfileExists: true }) diff --git a/apps/desktop/electron/connection-registry.ts b/apps/desktop/electron/connection-registry.ts index f3713570f0..c161cc265e 100644 --- a/apps/desktop/electron/connection-registry.ts +++ b/apps/desktop/electron/connection-registry.ts @@ -220,9 +220,7 @@ export interface RegistryLocalRoute { delegate: boolean /** Pool key for the forced-local child when not delegating. */ poolKey: string - /** Set when a concrete remote-only profile must not spawn a local child. - * Includes `default`: the ordinary existence guard exempts it, so a - * forced-local default spawn would otherwise succeed with no error. */ + /** Set when a concrete remote-only profile must not spawn a local child. */ refuse?: string } @@ -526,8 +524,9 @@ function normalizedSshTarget(route: { host?: unknown; port?: unknown; user?: unk * the BARE profile key by design, and that slot may already hold the v1 * route's REMOTE descriptor — so the forced-local child pools under the * `conn:local::` form instead (colons are invalid in profile names, - * so it cannot collide). A concrete profile that does not exist on this - * machine, including default, is refused instead of spawned. A per-profile + * so it cannot collide). A concrete named profile that does not exist on + * this machine is refused instead of spawned. `default` is `$HERMES_HOME` + * itself, so it always exists here and is never refused. A per-profile * remote override still delegates to the legacy profile route. */ export function resolveRegistryLocalRoute( @@ -551,12 +550,13 @@ export function resolveRegistryLocalRoute( if (opts.globalRemote) { const poolKey = `${backendScopePrefix(LOCAL_CONNECTION_ID)}${profileKey}` - // A concrete profile that does not exist on this machine is remote-only. - // Spawning it locally is the #90477 loop (and, for default, a silent - // success — the ordinary guard exempts default). Refuse instead. An - // unprofiled call is enumeration, not a dial, and a profile that exists - // locally still force-locals so "This device" does not dial the remote. - if (concrete && opts.localProfileExists === false) { + // A concrete named profile that does not exist on this machine is + // remote-only. Spawning it locally is the #90477 loop, so refuse. An + // unprofiled call is enumeration, not a dial. `default` lives at + // $HERMES_HOME, not profiles/default, so This device -> default always + // force-locals. A profile that exists locally still force-locals so + // "This device" does not dial the remote. + if (concrete && profileKey !== 'default' && opts.localProfileExists === false) { return { delegate: false, poolKey, refuse: `Profile "${profileKey}" no longer exists.` } } diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts index 7804fabd89..bf4bbc6739 100644 --- a/apps/desktop/electron/main.ts +++ b/apps/desktop/electron/main.ts @@ -11676,11 +11676,8 @@ async function spawnPoolBackend( // here, and logging "Starting" first left an orphaned line with no READY // and no exit — the exact undiagnosable burst signature in remote-gateway // user bundles (Aug 2026, Dash's report). - assertLocalProfileCanStart( - profile, - profileDeletionGate, - key => directoryExists(path.join(HERMES_HOME, 'profiles', key)), - { allowImplicitDefault: !opts.forceLocal } + assertLocalProfileCanStart(profile, profileDeletionGate, key => + directoryExists(path.join(HERMES_HOME, 'profiles', key)) ) rememberLog(`Starting Hermes backend for profile "${profile}" via ${backend.label}`) diff --git a/apps/desktop/electron/profile-delete-routing.test.ts b/apps/desktop/electron/profile-delete-routing.test.ts index 1b756788f3..2ef4720e31 100644 --- a/apps/desktop/electron/profile-delete-routing.test.ts +++ b/apps/desktop/electron/profile-delete-routing.test.ts @@ -140,16 +140,6 @@ test('assertLocalProfileCanStart rejects a delayed retry after the profile direc assert.doesNotThrow(() => assertLocalProfileCanStart('selena', gate, profile => profile === 'selena')) }) -test('assertLocalProfileCanStart does not exempt default when a forced-local spawn asks', () => { - const gate = new ProfileDeletionGate() - - assert.throws( - () => assertLocalProfileCanStart('default', gate, () => false, { allowImplicitDefault: false }), - /Profile "default" no longer exists/ - ) - assert.doesNotThrow(() => assertLocalProfileCanStart('default', gate, () => true, { allowImplicitDefault: false })) -}) - test('localProfilePoolKeys returns every local process scope for one profile', () => { assert.deepEqual(localProfilePoolKeys('Selena'), ['selena', 'conn:local::selena']) assert.deepEqual(localProfilePoolKeys(''), []) diff --git a/apps/desktop/electron/profile-delete-routing.ts b/apps/desktop/electron/profile-delete-routing.ts index 9b37e110ac..39bada976a 100644 --- a/apps/desktop/electron/profile-delete-routing.ts +++ b/apps/desktop/electron/profile-delete-routing.ts @@ -184,8 +184,7 @@ export class ProfileDeletionGate { export function assertLocalProfileCanStart( profile: unknown, gate: ProfileDeletionGate, - profileDirectoryExists: (profile: string) => boolean, - opts: { allowImplicitDefault?: boolean } = {} + profileDirectoryExists: (profile: string) => boolean ): void { const key = String(profile ?? '') .trim() @@ -193,12 +192,7 @@ export function assertLocalProfileCanStart( gate.assertCanStart(key) - // `default` is `$HERMES_HOME`, not `profiles/default`, so the ordinary local - // start exempts it. A forced-local spawn must not: that exemption is how a - // remote-only default starts a local agent with no error (#90477). - const implicitDefault = opts.allowImplicitDefault !== false && key === 'default' - - if (key && !implicitDefault && !profileDirectoryExists(key)) { + if (key && key !== 'default' && !profileDirectoryExists(key)) { throw new Error(`Profile "${key}" no longer exists.`) } }