{"record":{"id":"fade56031214fcf0","repo":"tinyhumansai/openhuman","slug":"transport-manager-unknown-profile-kind-kind","errorCode":null,"errorMessage":"[transport:manager] unknown profile kind: ${kind}","messagePattern":"\\[transport:manager\\] unknown profile kind: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/transport/TransportManager.ts","lineNumber":101,"sourceCode":"      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   */\n  private async raceLanAndTunnel(): Promise<CoreTransport> {\n    const { rpcUrl, channelId, corePubkey, sessionToken, pairingToken, devicePrivkey } =\n      this.profile;\n\n    if (!channelId || !corePubkey) {\n      throw new Error('[transport:manager] tunnel profile missing channelId or corePubkey');\n    }\n\n    const tunnelToken = sessionToken ?? pairingToken;\n    if (!tunnelToken) {\n      throw new Error('[transport:manager] tunnel profile missing sessionToken or pairingToken');","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/services/transport/TransportManager.ts#L83-L119","documentation":"getTransport() fell through all four known kinds (local, cloud, lan, tunnel) and hit the exhaustive-default throw. ConnectionProfile.kind is the literal union 'local' | 'lan' | 'tunnel' | 'cloud', so at type level this is unreachable — it only happens at runtime with data that bypassed the types.","triggerScenarios":"A persisted profile from a different app version containing a since-removed or since-added kind string (e.g. an old 'remote' kind); corrupted localStorage/persisted Redux data; JSON injected from tests or devtools; a newer backend writing kinds this build does not know.","commonSituations":"Downgrading the app while keeping persisted state; two products sharing the storage key; hand-edited state during debugging.","solutions":["Reset the persisted coreMode/profile state so a default { kind: 'local' } profile is rebuilt","Add a rehydrate guard/migration that maps unknown kinds to 'local' (Redux persist migrate or a transform)","If adding a new kind, extend the ConnectionProfile union and this switch in the same change"],"exampleFix":"// before\nconst kind = persisted.kind; // could be 'remote' from an old build\n// after\nconst KNOWN = ['local', 'lan', 'tunnel', 'cloud'] as const;\nconst kind = KNOWN.includes(persisted.kind) ? persisted.kind : 'local';","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"const PROFILE_KINDS = ['local', 'lan', 'tunnel', 'cloud'] as const;\ntype ProfileKind = (typeof PROFILE_KINDS)[number];\nfunction isProfileKind(v: unknown): v is ProfileKind {\n  return typeof v === 'string' && (PROFILE_KINDS as readonly string[]).includes(v);\n}","tryCatchPattern":"try { await manager.getTransport(); }\ncatch (err) {\n  if (err instanceof Error && err.message.includes('unknown profile kind')) {\n    dispatch(resetCoreMode()); // rebuild a clean { kind: 'local' } profile\n  } else throw err;\n}","preventionTips":["Guard rehydrated state with isProfileKind before it reaches TransportManager","When adding a profile kind, update union + switch + migrations in one commit","Reset persisted profiles on app downgrade rather than trusting foreign shapes"],"tags":["configuration","transport","connection-profile","state-migration"],"backgroundTag":"invalid-connection-profile","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}