{"record":{"id":"308f72eeecc40108","repo":"tinyhumansai/openhuman","slug":"transport-manager-lan-profile-missing-rpcurl","errorCode":null,"errorMessage":"[transport:manager] lan profile missing rpcUrl","messagePattern":"\\[transport:manager\\] lan profile missing rpcUrl","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/transport/TransportManager.ts","lineNumber":90,"sourceCode":"      const t = new LocalTransport(this.localRpcUrl, this.localToken);\n      log('[transport:manager] → LocalTransport');\n      return t;\n    }\n\n    if (kind === 'cloud') {\n      const { rpcUrl, sessionToken } = this.profile;\n      if (!rpcUrl) {\n        throw new Error('[transport:manager] cloud profile missing rpcUrl');\n      }\n      const t = new CloudHttpTransport(rpcUrl, sessionToken ?? null);\n      log('[transport:manager] → CloudHttpTransport rpcUrl=%s', rpcUrl);\n      return t;\n    }\n\n    if (kind === 'lan') {\n      const { rpcUrl } = this.profile;\n      if (!rpcUrl) {\n        throw new Error('[transport:manager] lan profile missing rpcUrl');\n      }\n      const t = new LanHttpTransport(rpcUrl);\n      log('[transport:manager] → LanHttpTransport rpcUrl=%s', rpcUrl);\n      return t;\n    }\n\n    if (kind === 'tunnel') {\n      return this.raceLanAndTunnel();\n    }\n\n    throw new Error(`[transport:manager] unknown profile kind: ${kind}`);\n  }\n\n  /**\n   * Race LAN (with 2 s timeout) against Tunnel.\n   * Whichever responds to `openhuman.ping` first wins.\n   * If LAN wins but later fails, caller should call reset() to re-race.\n   */","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/services/transport/TransportManager.ts#L72-L108","documentation":"Same manager guard for kind === 'lan': the profile claims LAN mode but carries no rpcUrl, so there is nothing to point LanHttpTransport at. The lan branch destructures only rpcUrl (LAN has no auth token in this transport).","triggerScenarios":"A LAN profile saved before the host's address was captured (pairing flow interrupted); persisted state migration dropping the field; manual construction with kind 'lan' but empty rpcUrl.","commonSituations":"User added the LAN host, left settings mid-flow, and the half-built profile persisted; the host's IP changed and a cleanup path nulled rpcUrl; fixtures/tests omitting the URL.","solutions":["Re-add the LAN host in Settings → Core connection so rpcUrl (http://<host>:<port>/rpc) is captured","Validate the profile shape before createTransportManager and discard incomplete ones (fall back to kind 'local')","If persisting programmatically, never write kind:'lan' without rpcUrl — write it in one atomic update"],"exampleFix":"// before\nconst profile = { kind: 'lan' };\n// after\nconst profile = { kind: 'lan', rpcUrl: 'http://192.168.1.20:5800/rpc' };","handlingStrategy":"validation","validationCode":"function assertLanProfileReady(p: ConnectionProfile): void {\n  if (p.kind === 'lan' && !p.rpcUrl) {\n    throw new Error('lan profile incomplete — capture the host rpcUrl before connecting');\n  }\n}","typeGuard":"function isCompleteLanProfile(p: ConnectionProfile): boolean {\n  return p.kind !== 'lan' || (typeof p.rpcUrl === 'string' && p.rpcUrl.length > 0);\n}","tryCatchPattern":null,"preventionTips":["Validate the LAN host with a ping before persisting the profile","Treat an empty rpcUrl in a lan profile as 'not onboarded' in UI, not as a connectable state","Persist the whole profile in one atomic update"],"tags":["configuration","transport","lan","connection-profile"],"backgroundTag":"invalid-connection-profile","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}