tinyhumansai/openhuman · error

[transport:manager] tunnel profile missing channelId or…

Error message

[transport:manager] tunnel profile missing channelId or corePubkey

What it means

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.

Solutions

  1. Re-pair the device through the devices/pairing flow so channelId and corePubkey are freshly issued and persisted
  2. Validate the tunnel profile fields before createTransportManager and route the user to re-pairing UI when missing
  3. If the core workspace was reset, delete the stale profile on the client first so pairing starts clean
Defensive patterns

Strategy: validation

Validate before calling

function assertTunnelProfileReady(p: ConnectionProfile): void {
  if (p.kind === 'tunnel' && (!p.channelId || !p.corePubkey)) {
    throw new Error('tunnel profile incomplete — re-pair the device to obtain channelId and corePubkey');
  }
}

Type guard

function isPairableTunnelProfile(p: ConnectionProfile): boolean {
  return p.kind !== 'tunnel' || (typeof p.channelId === 'string' && typeof p.corePubkey === 'string' && p.channelId.length > 0 && p.corePubkey.length > 0);
}

Prevention

When it happens

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

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

Related errors


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

Appendix: source

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

    if (kind === 'tunnel') {
      return this.raceLanAndTunnel();
    }

    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.

View on GitHub (pinned to 7491200858)