stablyai/orca · error

desktop returned no relay endpoint after credential install

Error message

desktop returned no relay endpoint after credential install

What it means

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.

Source

Thrown at mobile/src/transport/pre-profile-pairing-coordinator.ts:239

  if (isMethodNotFound(provision)) {
    if (winner.path !== 'direct') {
      throw new Error('relay pairing RPC unavailable after relay path authentication')
    }
    await dependencies.saveHost(baseHost(offer, hostId, hostName, now))
    await dependencies.clearJournal(journal.metadata.journalId)
    return { hostId }
  }
  const installed = DeviceCredentialInstalledSchema.parse(requireSuccess(provision))
  const endpoints = PairingGetEndpointsResultSchema.parse(
    requireSuccess(
      await winner.client.sendRequest('pairing.getEndpoints', {
        installReqId: journal.metadata.installReqId
      })
    )
  )
  assertCommittedInstall(endpoints.installStatus, installed)
  if (!endpoints.relay) {
    throw new Error('desktop returned no relay endpoint after credential install')
  }
  assertActive(isDisposed)
  await dependencies.writeCredentialBundle(promotePairingJournalCredential({ journal, installed }))
  await dependencies.saveHost(relayHost(journal, endpoints.relay))
  await dependencies.clearJournal(journal.metadata.journalId)
  return { hostId }
}

function baseHost(
  offer: PairingOffer,
  hostId: string,
  name: string,
  lastConnected: number
): HostProfile {
  return {
    id: hostId,
    name,
    endpoint: offer.endpoint,

View on GitHub (pinned to 1136503c6a)

Solutions

  1. On the desktop, verify relay endpoint publication is enabled and that getEndpoints returns a non-null relay after provisionRelay commits.
  2. Inspect the desktop logs around the provisionRelay→getEndpoints sequence for relay-publishing errors or skipped steps.
  3. 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.
  4. Reproduce by calling pairing.getEndpoints directly against the desktop immediately after provisionRelay to confirm the omission.
Defensive patterns

Strategy: validation

Validate before calling

// After getEndpoints, verify relay is present before relying on it
function hasRelayEndpoint(endpoints: unknown): endpoints is { relay: MobileRelayEndpoint } {
  return typeof endpoints === 'object' && endpoints !== null && 'relay' in endpoints && !!endpoints.relay
}

Type guard

function isNoRelayEndpointError(error: unknown): boolean {
  return error instanceof Error
    && error.message === 'desktop returned no relay endpoint after credential install'
}

Try / catch

try {
  await runPairing(...)
} catch (error) {
  if (isNoRelayEndpointError(error)) {
    // desktop didn't publish relay — re-pair without relay to save a direct-only host
    return startPreProfilePairing({ ...args, offer: { ...args.offer, relay: undefined } })
  }
  throw error
}

Prevention

When it happens

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

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

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/6968519efd6d72d8. Report an issue: GitHub.