{"record":{"id":"6968519efd6d72d8","repo":"stablyai/orca","slug":"desktop-returned-no-relay-endpoint-after-credentia","errorCode":null,"errorMessage":"desktop returned no relay endpoint after credential install","messagePattern":"desktop returned no relay endpoint after credential install","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mobile/src/transport/pre-profile-pairing-coordinator.ts","lineNumber":239,"sourceCode":"  if (isMethodNotFound(provision)) {\n    if (winner.path !== 'direct') {\n      throw new Error('relay pairing RPC unavailable after relay path authentication')\n    }\n    await dependencies.saveHost(baseHost(offer, hostId, hostName, now))\n    await dependencies.clearJournal(journal.metadata.journalId)\n    return { hostId }\n  }\n  const installed = DeviceCredentialInstalledSchema.parse(requireSuccess(provision))\n  const endpoints = PairingGetEndpointsResultSchema.parse(\n    requireSuccess(\n      await winner.client.sendRequest('pairing.getEndpoints', {\n        installReqId: journal.metadata.installReqId\n      })\n    )\n  )\n  assertCommittedInstall(endpoints.installStatus, installed)\n  if (!endpoints.relay) {\n    throw new Error('desktop returned no relay endpoint after credential install')\n  }\n  assertActive(isDisposed)\n  await dependencies.writeCredentialBundle(promotePairingJournalCredential({ journal, installed }))\n  await dependencies.saveHost(relayHost(journal, endpoints.relay))\n  await dependencies.clearJournal(journal.metadata.journalId)\n  return { hostId }\n}\n\nfunction baseHost(\n  offer: PairingOffer,\n  hostId: string,\n  name: string,\n  lastConnected: number\n): HostProfile {\n  return {\n    id: hostId,\n    name,\n    endpoint: offer.endpoint,","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/mobile/src/transport/pre-profile-pairing-coordinator.ts#L221-L257","documentation":"Thrown in runPairing after a successful provisionRelay when the follow-up pairing.getEndpoints response parsed cleanly (PairingGetEndpointsResultSchema) but its `relay` field is falsy. The desktop installed the credential (assertCommittedInstall passed) yet did not publish the relay endpoint the mobile side needs to persist a relay-capable host profile. Without endpoints.relay there is no relayHost to save (relayWebSocketUrl at line 278-282 needs relay.cellUrl and relayHostId), so pairing cannot complete.","triggerScenarios":"Desktop returned { ok: true, result: { installStatus: { state:'committed', result }, relay: null } } (or omitted relay). assertCommittedInstall(endpoints.installStatus, installed) already passed, so install is consistent — only the endpoint advertisement is missing. Caused by server-side logic that provisions credentials but skips/defers relay endpoint publication, or a desktop config that disabled relay publishing post-install.","commonSituations":"Hits after a desktop-side change to relay endpoint configuration (relay disabled in host settings but credential install still succeeds), or against a desktop build where getEndpoints omits relay under certain feature flags. Mobile users see pairing fail at the very last step after a long successful handshake.","solutions":["On the desktop, verify relay endpoint publication is enabled and that getEndpoints returns a non-null relay after provisionRelay commits.","Inspect the desktop logs around the provisionRelay→getEndpoints sequence for relay-publishing errors or skipped steps.","If relay is intentionally disabled on the desktop, pair without offer.relay so the flow takes the direct-only branch (line 203-206) and never calls getEndpoints.","Reproduce by calling pairing.getEndpoints directly against the desktop immediately after provisionRelay to confirm the omission."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// After getEndpoints, verify relay is present before relying on it\nfunction hasRelayEndpoint(endpoints: unknown): endpoints is { relay: MobileRelayEndpoint } {\n  return typeof endpoints === 'object' && endpoints !== null && 'relay' in endpoints && !!endpoints.relay\n}","typeGuard":"function isNoRelayEndpointError(error: unknown): boolean {\n  return error instanceof Error\n    && error.message === 'desktop returned no relay endpoint after credential install'\n}","tryCatchPattern":"try {\n  await runPairing(...)\n} catch (error) {\n  if (isNoRelayEndpointError(error)) {\n    // desktop didn't publish relay — re-pair without relay to save a direct-only host\n    return startPreProfilePairing({ ...args, offer: { ...args.offer, relay: undefined } })\n  }\n  throw error\n}","preventionTips":["On the desktop, ensure relay endpoint publication is enabled whenever provisionRelay is implemented.","If relay is intentionally disabled, pair without offer.relay to avoid reaching getEndpoints.","Monitor desktop logs for the provisionRelay→getEndpoints sequence to catch silent omissions."],"tags":["pairing","relay","rpc","desktop","contract-violation"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}