stablyai/orca · error · Error

relay credential rotation endpoint state missing

Error message

relay credential rotation endpoint state missing

What it means

Thrown near the end of `rotateMobileRelayCredential` when `endpoints.relay` is missing or the install is not in `committed` state. Even if `provisionRelay` succeeded, the rotation cannot complete without an authoritative relay endpoint to publish alongside the credential.

Source

Thrown at mobile/src/transport/mobile-relay-credential-rotation.ts:69

    const response = await args.client.sendRequest('pairing.provisionRelay', {
      reqId: pending.reqId,
      newResumeTokenHash: pending.hash,
      expectedCurrentHash: bundle.current.hash
    })
    if (!response.ok) {
      throw new Error(`${response.error.code}: ${response.error.message}`)
    }
    const installed = DeviceCredentialInstalledSchema.parse(response.result)
    endpoints = await getEndpoints(args.client, pending.reqId)
    if (
      endpoints.installStatus?.state !== 'committed' ||
      JSON.stringify(endpoints.installStatus.result) !== JSON.stringify(installed)
    ) {
      throw new Error('relay credential rotation was not authoritatively committed')
    }
  }
  if (!endpoints.relay || endpoints.installStatus?.state !== 'committed') {
    throw new Error('relay credential rotation endpoint state missing')
  }
  const installed = endpoints.installStatus.result
  const next = MobileRelayCredentialBundleSchema.parse({
    ...bundle,
    current: {
      token: pending.token,
      hash: pending.hash,
      version: installed.currentVersion,
      expiresAt: installed.resumeExpiresAt
    },
    ...(installed.graceExpiresAt
      ? { grace: { ...bundle.current, expiresAt: installed.graceExpiresAt } }
      : { grace: undefined }),
    pending: undefined
  })
  await args.writeBundle(next)
  return { bundle: next, relay: endpoints.relay }
}

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Retry `getEndpoints` after a delay to allow the relay cell to be allocated.
  2. If `relay` is persistently absent, surface a relay-unavailable error to the user and keep the prior credential.
  3. Verify with the server operator that relay provisioning is healthy.

Example fix

// before
// proceeds to writeBundle with undefined relay -> throws

// after
for (let i = 0; i < 3 && (!endpoints.relay || endpoints.installStatus?.state !== 'committed'); i++) {
  await delay(500)
  endpoints = await getEndpoints(args.client, pending.reqId)
}
if (!endpoints.relay) throw new Error('relay endpoint never allocated')
Defensive patterns

Strategy: retry

Validate before calling

// Probe for relay presence before promoting:
const endpoints = await getEndpoints(client, pending.reqId)
if (!endpoints.relay) { await delay(500); /* re-check */ }

Type guard

function hasRelayEndpoint(e: { relay?: unknown }): boolean { return !!e.relay }

Try / catch

try { return await rotateMobileRelayCredential(args) } catch (e) { if (e.message === 'relay credential rotation endpoint state missing') { scheduleRetry(args); return null } throw e }

Prevention

When it happens

Trigger: Server returned `committed` install status but no `relay` endpoint object; install reverted to a non-committed state between the two `getEndpoints` calls; server allocated the credential but not the relay cell.

Common situations: Partial server-side provisioning; a relay cell that was deallocated; misconfigured relay that reports install success without a routable endpoint.

Related errors


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