diff --git a/electron/gateway/manager.ts b/electron/gateway/manager.ts index cf96d3c25..eec06d333 100644 --- a/electron/gateway/manager.ts +++ b/electron/gateway/manager.ts @@ -337,7 +337,11 @@ export class GatewayManager extends EventEmitter { }); try { + let startAttempts = 0; + const MAX_START_ATTEMPTS = 3; + while (true) { + startAttempts++; this.assertLifecycleEpoch(startEpoch, 'start'); this.recentStartupStderrLines = []; try { @@ -390,6 +394,22 @@ export class GatewayManager extends EventEmitter { } logger.error('OpenClaw doctor repair failed; not retrying Gateway startup'); } + + // Retry on transient connect errors + const errMsg = String(error); + const isTransientError = + errMsg.includes('WebSocket closed before handshake') || + errMsg.includes('ECONNREFUSED') || + errMsg.includes('Gateway process exited before becoming ready') || + errMsg.includes('Timed out waiting for connect.challenge') || + errMsg.includes('Connect handshake timeout'); + + if (startAttempts < MAX_START_ATTEMPTS && isTransientError) { + logger.warn(`Transient start error: ${errMsg}. Retrying... (${startAttempts}/${MAX_START_ATTEMPTS})`); + await new Promise((r) => setTimeout(r, 1000)); + continue; + } + throw error; } } diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 891c127e0..ae3bc1af2 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -1312,7 +1312,10 @@ function registerProviderHandlers(gatewayManager: GatewayManager): void { } // Debounced restart so the gateway picks up the new default provider. - if (gatewayManager.isConnected()) { + // Because OAuth success triggers a debounced restart, the gateway might not be + // currently connected ('starting' or 'reconnecting'). Checking if it is simply + // not 'stopped' ensures the restart request is correctly queued or coalesced. + if (gatewayManager.getStatus().state !== 'stopped') { logger.info(`Scheduling Gateway restart after provider switch to "${ock}"`); gatewayManager.debouncedRestart(); }