tinyhumansai/openhuman · error

[transport:manager] tunnel profile missing sessionToken or…

Error message

[transport:manager] tunnel profile missing sessionToken or pairingToken

What it means

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.

Solutions

  1. Re-pair the device to obtain a fresh pairingToken (or a new sessionToken) — there is no way to refresh it from the client alone
  2. If you persisted the profile yourself, store sessionToken immediately after the first successful tunnel connect
  3. Treat this error in UI as 'device needs re-pairing', not as a network failure
Defensive patterns

Strategy: validation

Validate before calling

function hasTunnelCredentials(p: ConnectionProfile): boolean {
  return p.kind !== 'tunnel' || Boolean(p.sessionToken || p.pairingToken);
}

Try / catch

try { await manager.getTransport(); }
catch (err) {
  if (err instanceof Error && err.message.includes('missing sessionToken or pairingToken')) {
    await startRePairing(); // token is gone server-side; only re-pair can fix it
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/32c1f60dd95229ca. Report an issue: GitHub.

Appendix: source

Thrown at app/src/services/transport/TransportManager.ts:119

    throw new Error(`[transport:manager] unknown profile kind: ${kind}`);
  }

  /**
   * Race LAN (with 2 s timeout) against Tunnel.
   * Whichever responds to `openhuman.ping` first wins.
   * If LAN wins but later fails, caller should call reset() to re-race.
   */
  private async raceLanAndTunnel(): Promise<CoreTransport> {
    const { rpcUrl, channelId, corePubkey, sessionToken, pairingToken, devicePrivkey } =
      this.profile;

    if (!channelId || !corePubkey) {
      throw new Error('[transport:manager] tunnel profile missing channelId or corePubkey');
    }

    const tunnelToken = sessionToken ?? pairingToken;
    if (!tunnelToken) {
      throw new Error('[transport:manager] tunnel profile missing sessionToken or pairingToken');
    }

    const tunnelTransport = new TunnelTransport(
      this.backendSocketUrl,
      channelId,
      corePubkey,
      tunnelToken,
      devicePrivkey,
      sessionToken ? 'session' : 'pairing'
    );

    if (!rpcUrl) {
      // No LAN URL — tunnel only.
      log('[transport:manager] → TunnelTransport (no LAN URL)');
      return tunnelTransport;
    }

    const lanTransport = new LanHttpTransport(rpcUrl, LAN_RACE_TIMEOUT_MS);

View on GitHub (pinned to 7491200858)