diff --git a/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx b/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx
index 2db75c8bfe880..3d1d8f9c0124d 100644
--- a/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx
+++ b/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx
@@ -2,6 +2,7 @@ import { act, cleanup, render } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { $desktopBoot } from '@/store/boot'
+import { $activeGatewayProfile } from '@/store/profile'
import { $gatewayState } from '@/store/session'
import { useGatewayBoot } from './use-gateway-boot'
@@ -122,6 +123,7 @@ beforeEach(() => {
FakeWebSocket.instances = []
;(globalThis as { WebSocket: unknown }).WebSocket = FakeWebSocket
;(window as { hermesDesktop?: unknown }).hermesDesktop = fakeDesktop()
+ $activeGatewayProfile.set('default')
$gatewayState.set('idle')
$desktopBoot.set({
error: null,
@@ -224,6 +226,18 @@ describe('useGatewayBoot remote reconnect loop (real hook, fake socket)', () =>
expect(FakeWebSocket.instances.length).toBeGreaterThan(1)
})
+ it('seeds the primary gateway profile from the desktop preference before connecting', async () => {
+ const desktop = fakeDesktop()
+ desktop.profile.get = vi.fn(async () => ({ profile: 'coder' }))
+ ;(window as { hermesDesktop?: unknown }).hermesDesktop = desktop
+
+ render()
+ await flushAsync()
+
+ expect($activeGatewayProfile.get()).toBe('coder')
+ expect(desktop.profile.get).toHaveBeenCalled()
+ })
+
it('FIX: after the prolonged drop the hook raises a recoverable boot error (the escape hatch)', async () => {
render()
await flushAsync()
diff --git a/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts b/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts
index 593e7a36f748d..6e87adb2067c6 100644
--- a/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts
+++ b/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts
@@ -222,6 +222,20 @@ export function useGatewayBoot({
// Secondary (background-profile) sockets funnel into the same handler.
configureGatewayRegistry({ onEvent: event => callbacksRef.current.handleGatewayEvent(event) })
+ const seedPrimaryProfileFromPreference = async () => {
+ try {
+ const pref = await desktop.profile?.get?.()
+ const profileKey = normalizeProfileKey(pref?.profile)
+
+ $activeGatewayProfile.set(profileKey)
+ setPrimaryGateway(gateway, profileKey)
+
+ return profileKey
+ } catch {
+ return null
+ }
+ }
+
const offState = gateway.onState(st => {
// Mirror to the composer only while the primary is the active profile —
// a background secondary reconnect mustn't flip the foreground state.
@@ -315,6 +329,7 @@ export function useGatewayBoot({
async function boot() {
try {
+ await seedPrimaryProfileFromPreference()
const conn = await desktop.getConnection()
if (cancelled) {
@@ -343,10 +358,7 @@ export function useGatewayBoot({
// same-profile resumes are no-op swaps and any reconnect targets the
// right backend. Best-effort: a missing preference means "default".
try {
- const pref = await desktop.profile?.get?.()
- const profileKey = (pref?.profile ?? '').trim() || 'default'
- $activeGatewayProfile.set(profileKey)
- setPrimaryGateway(gateway, profileKey)
+ const profileKey = (await seedPrimaryProfileFromPreference()) || 'default'
void ensureGatewayForProfile(profileKey)
} catch {
$activeGatewayProfile.set('default')