{"record":{"id":"cae4e3af1590aefd","repo":"tinyhumansai/openhuman","slug":"transport-manager-all-transports-failed-to-conne","errorCode":null,"errorMessage":"[transport:manager] all transports failed to connect","messagePattern":"\\[transport:manager\\] all transports failed to connect","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"app/src/services/transport/TransportManager.ts","lineNumber":174,"sourceCode":"    const winner = await Promise.race([lanRace, tunnelRace]);\n\n    if (winner) {\n      // Close the losing transport.\n      void winner.loser.close().catch(() => {});\n      log('[transport:manager] race winner: %s', winner.transport.kind);\n      return winner.transport;\n    }\n\n    // Both failed in the race window — wait for whichever succeeds.\n    logErr('[transport:manager] race: both transports unhealthy; waiting…');\n    const result = await Promise.any([lanRace, tunnelRace]);\n    if (result) {\n      void result.loser.close().catch(() => {});\n      log('[transport:manager] fallback winner: %s', result.transport.kind);\n      return result.transport;\n    }\n\n    throw new Error('[transport:manager] all transports failed to connect');\n  }\n}\n\n// -- convenience factory ------------------------------------------------------\n\n/**\n * Build a TransportManager from a ConnectionProfile.\n * `localRpcUrl` / `localToken` are only needed for kind=\"local\".\n */\nexport function createTransportManager(\n  profile: ConnectionProfile,\n  opts: {\n    localRpcUrl?: () => Promise<string>;\n    localToken?: () => Promise<string | null>;\n    backendSocketUrl?: string;\n  } = {}\n): TransportManager {\n  const noop = () => Promise.resolve(null);","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/services/transport/TransportManager.ts#L156-L192","documentation":"raceLanAndTunnel() first races LAN isHealthy (2s LAN_RACE_TIMEOUT_MS) against tunnel isHealthy; when both resolve null it awaits Promise.any for a late winner, and if that also yields null it gives up: neither the LAN HTTP ping nor the tunnel handshake ever succeeded. This is the terminal 'no path to the core' error for tunnel profiles.","triggerScenarios":"Desktop host unreachable on LAN (asleep, off, IP changed) AND the backend relay rejecting the tunnel (invalid/expired sessionToken, channel closed) or the relay itself down; both transports' openhuman.ping health checks failing.","commonSituations":"Remote usage where the home machine is off; device session revoked while also off-LAN; backend outage; firewall blocking both the LAN port and websocket egress.","solutions":["Call manager.reset() and retry once — the doc comment explicitly designs reset() for re-racing after a winner later failed","Verify the desktop core is running and on the same network, then retry while on LAN","Check tunnel credentials (re-pair if the session was revoked) via the error surfaced by the losing tunnelRace","Fall back to another profile (local on the desktop itself, or cloud) until connectivity returns"],"exampleFix":"// before\nconst t = await manager.getTransport();\n// after\nlet t;\ntry {\n  t = await manager.getTransport();\n} catch (err) {\n  manager.reset();\n  t = await manager.getTransport(); // re-race LAN vs Tunnel once\n}","handlingStrategy":"retry","validationCode":"// Cheap reachability split before the expensive race:\nasync function lanReachable(rpcUrl: string): Promise<boolean> {\n  try { await fetch(rpcUrl, { method: 'HEAD' }); return true; } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await manager.getTransport();\n} catch (err) {\n  if (err instanceof Error && err.message.includes('all transports failed')) {\n    manager.reset();            // documented recovery: re-race LAN vs Tunnel\n    await sleep(2_000);\n    return await manager.getTransport();\n  }\n  throw err;\n}","preventionTips":["Call manager.reset() before re-racing — a previously-won-but-dead transport is cached otherwise","Retry only a bounded number of times, then fall back to another profile","Log which transport won previously; a LAN winner that later died is the designed reset() case"],"tags":["transport","tunnel","lan","connection-failure","resilience"],"backgroundTag":"transport-connect-failed","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}