stablyai/orca · error · Error
relay credential rotation pending state missing
Error message
relay credential rotation pending state missing
What it means
Thrown in `rotateMobileRelayCredential` when `bundle.pending` is missing after the block that creates and persists it. The preceding code writes `bundle` with a `pending` field via the schema-strict parse, so reaching this throw implies the schema stripped `pending` (e.g. the parser set it to undefined because the strict object rejected it) or a caller passed a pre-rotated bundle whose `pending` was already consumed. It is a defensive invariant guard.
Source
Thrown at mobile/src/transport/mobile-relay-credential-rotation.ts:47
if (!bundle.pending) {
const randomBytes = args.randomBytes ?? ExpoCrypto.getRandomBytes
const token = encodeBase64Url(randomBytes(32))
bundle = MobileRelayCredentialBundleSchema.parse({
...bundle,
pending: {
token,
hash: hashMobileRelayCredential(token),
reqId: `rotate-${encodeBase64Url(randomBytes(16))}`
}
})
// Why: a crash or lost response must leave enough material to query the
// one global install key before any second authorization attempt.
await args.writeBundle(bundle)
}
const pending = bundle.pending
if (!pending) {
throw new Error('relay credential rotation pending state missing')
}
let endpoints = await getEndpoints(args.client, pending.reqId)
if (endpoints.installStatus?.state !== 'committed') {
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')View on GitHub (pinned to 1136503c6a)
Solutions
- Confirm the bundle passed in either already has a valid `pending` or no `pending` (in which case rotation creates one).
- If this fires, suspect a schema regression in `MobileRelayCredentialBundleSchema` around the `pending` field.
- Add a regression test that asserts `pending` is present after the create-and-write block.
Example fix
// before
const bundle = await readBundle() // pending undefined unexpectedly
await rotateMobileRelayCredential({ client, bundle, writeBundle })
// after
if (!bundle.pending && !bundle.current) throw new Error('bundle has neither pending nor current')
// the rotation function itself is the correct path; this throw indicates corruption upstream Defensive patterns
Strategy: validation
Validate before calling
function bundleHasPending(b: MobileRelayCredentialBundle): boolean { return !!b.pending }
if (!bundleHasPending(bundle) && !bundle.current) { throw new Error('bundle corrupted') } Type guard
function hasPending(b: MobileRelayCredentialBundle): b is MobileRelayCredentialBundle & { pending: NonNullable<MobileRelayCredentialBundle['pending']> } { return !!b.pending } Prevention
- Treat this throw as an internal invariant violation — investigate schema regressions.
- Never mutate the bundle object between writeBundle and the pending read.
- Add regression tests covering the create-and-write path.
When it happens
Trigger: Caller passes a bundle where `pending` was set to `undefined` in a way that survived schema parse; `writeBundle` mutated the in-memory object; a schema change that made `pending` non-optional in the wrong direction.
Common situations: Internal logic error or future refactor that drops the pending write; extremely unlikely under normal control flow because the constructor block always sets `pending`.
Related errors
- ${response.error.code}: ${response.error.message}
- relay credential rotation was not authoritatively committed
- relay credential rotation endpoint state missing
- relay credential install result does not match pairing journ
- direct pairing upgrade was not authoritatively committed
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/bc1c8ec99ca551b5.
Report an issue: GitHub.