{"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":"Error","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/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/transport/TransportManager.ts#L96-L132","documentation":"A kind 'tunnel' profile must identify the relay channel (channelId) and the core's public key (corePubkey) so TunnelTransport can route and encrypt; missing either makes the tunnel unusable, so raceLanAndTunnel refuses before racing LAN against tunnel.","triggerScenarios":"getTransport() on a tunnel profile saved before pairing completed — the flow was interrupted after channel creation but before the core pubkey exchange, so one of the two fields is empty.","commonSituations":"Interrupted device pairing (security/devices domain); partial profile persistence from a crash mid-write; test fixtures missing tunnel fields.","solutions":["Re-run device pairing so both channelId and corePubkey are recorded","Delete the half-paired device entry and pair again","Add a load-time completeness assertion (see guard) that fails with the profile id"],"exampleFix":"// before\nconst tm = createTransportManager(profile, opts);\n\n// after\nif (profile.kind === 'tunnel' && (!profile.channelId || !profile.corePubkey)) {\n  throw new Error(`profile ${profile.id}: tunnel pairing incomplete — re-pair the device`);\n}\nconst tm = createTransportManager(profile, opts);","handlingStrategy":"validation","validationCode":"function assertTunnelProfilePaired(p: ConnectionProfile): void {\n  if (p.kind === 'tunnel' && (!p.channelId || !p.corePubkey)) {\n    throw new Error(`profile ${p.id}: tunnel pairing incomplete (channelId/corePubkey missing) — re-pair`);\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Write channelId and corePubkey atomically — never persist a half-paired profile","On interrupted pairing, delete the partial device entry before retrying","Assert tunnel profile completeness at load time"],"tags":["transport","tunnel","pairing","security-devices","config"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}