{"record":{"id":"32c1f60dd95229ca","repo":"tinyhumansai/openhuman","slug":"transport-manager-tunnel-profile-missing-session","errorCode":null,"errorMessage":"[transport:manager] tunnel profile missing sessionToken or pairingToken","messagePattern":"\\[transport:manager\\] tunnel profile missing sessionToken or pairingToken","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/transport/TransportManager.ts","lineNumber":119,"sourceCode":"    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.\n      log('[transport:manager] → TunnelTransport (no LAN URL)');\n      return tunnelTransport;\n    }\n\n    const lanTransport = new LanHttpTransport(rpcUrl, LAN_RACE_TIMEOUT_MS);","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/services/transport/TransportManager.ts#L101-L137","documentation":"raceLanAndTunnel() needs an auth token for the backend relay: sessionToken (reconnect path) or, failing that, pairingToken (first-connect path). Neither present means the profile cannot authenticate the socket.io tunnel:connect handshake at all. sessionToken ? 'session' : 'pairing' selects which field the relay checks.","triggerScenarios":"Session expired/rotated and the one-time pairing token was already consumed on an earlier connect (pairing tokens are single-use); logout cleared sessionToken without clearing the profile; pairing completed but tokens never persisted.","commonSituations":"Returning to a tunnel connection days later after the session lapsed; core revoked the device's session (devices domain) but the client still shows the profile; interrupted first-connect consumed the pairing token without delivering the session token.","solutions":["Re-pair the device to obtain a fresh pairingToken (or a new sessionToken) — there is no way to refresh it from the client alone","If you persisted the profile yourself, store sessionToken immediately after the first successful tunnel connect","Treat this error in UI as 'device needs re-pairing', not as a network failure"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"function hasTunnelCredentials(p: ConnectionProfile): boolean {\n  return p.kind !== 'tunnel' || Boolean(p.sessionToken || p.pairingToken);\n}","typeGuard":null,"tryCatchPattern":"try { await manager.getTransport(); }\ncatch (err) {\n  if (err instanceof Error && err.message.includes('missing sessionToken or pairingToken')) {\n    await startRePairing(); // token is gone server-side; only re-pair can fix it\n  } else throw err;\n}","preventionTips":["Persist sessionToken immediately after the first successful tunnel connect — pairing tokens are single-use","Clear tunnel profiles on logout so a stale profile cannot be retried","Surface this as 'device needs re-pairing' in UI"],"tags":["configuration","transport","tunnel","authentication","pairing"],"backgroundTag":"invalid-connection-profile","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}