{"record":{"id":"c5687cd7ed6d8821","repo":"tinyhumansai/openhuman","slug":"transport-manager-tunnel-profile-missing-channel","errorCode":null,"errorMessage":"[transport:manager] tunnel profile missing channelId or corePubkey","messagePattern":"\\[transport:manager\\] tunnel profile missing channelId or corePubkey","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/transport/TransportManager.ts","lineNumber":114,"sourceCode":"\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');\n    }\n\n    const tunnelTransport = new TunnelTransport(\n      this.backendSocketUrl,\n      channelId,\n      corePubkey,\n      tunnelToken,\n      devicePrivkey,\n      sessionToken ? 'session' : 'pairing'\n    );\n\n    if (!rpcUrl) {\n      // No LAN URL — tunnel only.","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/services/transport/TransportManager.ts#L96-L132","documentation":"For kind === 'tunnel' the manager's raceLanAndTunnel() destructures channelId and corePubkey from the profile and refuses to proceed without both — they are the minimum to address the desktop core through the backend relay (channelId names the tunnel; corePubkey seals the first handshake frame). Without them the E2E-encrypted XChaCha20-Poly1305 path cannot even start.","triggerScenarios":"Device pairing completed only partially (channel allocated but core pubkey never recorded); persisted tunnel profile trimmed by a migration; pairing record for this device deleted on the core side (src/openhuman/security/devices/) while the client kept the profile.","commonSituations":"Pairing flow interrupted (app closed during pair); core workspace reset wiping devices.db while the phone/iOS client kept its tunnel profile; schema change renaming fields.","solutions":["Re-pair the device through the devices/pairing flow so channelId and corePubkey are freshly issued and persisted","Validate the tunnel profile fields before createTransportManager and route the user to re-pairing UI when missing","If the core workspace was reset, delete the stale profile on the client first so pairing starts clean"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"function assertTunnelProfileReady(p: ConnectionProfile): void {\n  if (p.kind === 'tunnel' && (!p.channelId || !p.corePubkey)) {\n    throw new Error('tunnel profile incomplete — re-pair the device to obtain channelId and corePubkey');\n  }\n}","typeGuard":"function isPairableTunnelProfile(p: ConnectionProfile): boolean {\n  return p.kind !== 'tunnel' || (typeof p.channelId === 'string' && typeof p.corePubkey === 'string' && p.channelId.length > 0 && p.corePubkey.length > 0);\n}","tryCatchPattern":null,"preventionTips":["Persist channelId + corePubkey together at pairing completion, not incrementally","If the core workspace is reset, clear tunnel profiles on the client in the same operation","Route users to the pairing UI when either field is missing instead of attempting connect"],"tags":["configuration","transport","tunnel","pairing","e2e-encryption"],"backgroundTag":"invalid-connection-profile","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}