From 6ec0c1affb488c4fb4bfbfa7db3c8bc1347d412c Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Thu, 16 Jul 2026 02:12:22 -0700 Subject: [PATCH] fix(serve): prefer explicit --port over stale WS fallback (#8535) When `orca serve --port
` is set, bind the pinned port before any persisted mobile-ws-fallback-port.json entry so clients dialing
are not stranded. Default auto/desktop keep STA-1511 fallback-first. --- src/main/index.ts | 10 +++++- src/main/runtime/rpc/ws-transport.test.ts | 37 +++++++++++++++++++++++ src/main/runtime/rpc/ws-transport.ts | 26 ++++++++++++---- src/main/runtime/runtime-rpc.ts | 16 +++++++--- 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 21c4a9ab51e..ded536c3e32 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -2119,7 +2119,15 @@ app.whenReady().then(async () => { enableWebSocket: true, ...(isE2E ? { wsPort: 0 } : {}), ...(devWsPort !== undefined ? { wsPort: devWsPort } : {}), - ...(serveOptions?.wsPort !== undefined ? { wsPort: serveOptions.wsPort } : {}), + ...(serveOptions?.wsPort !== undefined + ? { + wsPort: serveOptions.wsPort, + // Why: only an explicit `orca serve --port` pin prefers the requested + // port over a stale STA-1511 fallback (issue #8535). Default 6768 / + // dev 6769 keep fallback-first so mobile pairings stay stable. + preferPinnedWsPort: true + } + : {}), webClientRoot: getBundledWebClientRoot() }) registerMobileHandlers(runtimeRpc, { getRelayStatus: () => desktopRelayStatus }) diff --git a/src/main/runtime/rpc/ws-transport.test.ts b/src/main/runtime/rpc/ws-transport.test.ts index 391e5e18284..ca34340ccb1 100644 --- a/src/main/runtime/rpc/ws-transport.test.ts +++ b/src/main/runtime/rpc/ws-transport.test.ts @@ -472,6 +472,43 @@ describe('WebSocketTransport', () => { expect(transport.resolvedPort).toBe(fallbackPort) }) + it('binds the preferred port first when preferPinnedPort is set and both are free', async () => { + // Why: issue #8535 — `orca serve --port
` clients dial the pin. A + // free but stale mobile-ws-fallback-port.json must not pre-empt it. + const preferredPort = await reserveFreePort() + const fallbackPort = await reserveFreePort() + + const transport = new WebSocketTransport({ + host: '127.0.0.1', + port: preferredPort, + fallbackPort, + preferPinnedPort: true + }) + transports.push(transport) + await transport.start() + expect(transport.resolvedPort).toBe(preferredPort) + }) + + it('falls back when preferPinnedPort is set but the preferred port is taken', async () => { + // Why: explicit pins still degrade to the STA-1511 fallback on + // EADDRINUSE so previously-paired mobile devices remain reachable. + const preferredHolder = new WebSocketTransport({ host: '127.0.0.1', port: 0 }) + transports.push(preferredHolder) + await preferredHolder.start() + const preferredPort = preferredHolder.resolvedPort + const fallbackPort = await reserveFreePort() + + const transport = new WebSocketTransport({ + host: '127.0.0.1', + port: preferredPort, + fallbackPort, + preferPinnedPort: true + }) + transports.push(transport) + await transport.start() + expect(transport.resolvedPort).toBe(fallbackPort) + }) + it('binds the preferred port when the persisted fallback is taken', async () => { const fallbackHolder = new WebSocketTransport({ host: '127.0.0.1', port: 0 }) transports.push(fallbackHolder) diff --git a/src/main/runtime/rpc/ws-transport.ts b/src/main/runtime/rpc/ws-transport.ts index 8b88c7a820e..0d5403c5991 100644 --- a/src/main/runtime/rpc/ws-transport.ts +++ b/src/main/runtime/rpc/ws-transport.ts @@ -54,6 +54,11 @@ export type WebSocketTransportOptions = { // the (now free) preferred port instead would strand those pairings // (STA-1511). Callers pass the previously assigned fallback port here. fallbackPort?: number + // Why: `orca serve --port
` clients dial the pinned port. Prefer that port
+ // first (fallback second) so a stale mobile-ws-fallback-port.json cannot
+ // silently steal the pin (issue #8535). Default auto/desktop keeps
+ // fallback-first for STA-1511 pairing stability.
+ preferPinnedPort?: boolean
}
export class WebSocketTransport implements RpcTransport {
@@ -65,6 +70,7 @@ export class WebSocketTransport implements RpcTransport {
private readonly preAuthTimeoutMs: number
private readonly staticRoot: string | undefined
private readonly fallbackPort: number | undefined
+ private readonly preferPinnedPort: boolean
private httpServer: HttpsServer | HttpServer | null = null
private wss: WebSocketServer | null = null
private heartbeatTimer: ReturnType