stablyai/orca · error

mobile relay pairing recovery pending

Error message

mobile relay pairing recovery pending

What it means

Thrown inside the serialized save mutation when an existing journal metadata is found with a different journalId that already has winner or authorizationMode set. A prior pairing advanced past the authorization commit point, so overwriting it would abandon an in-flight install/recovery; the save is refused.

Source

Thrown at mobile/src/transport/mobile-relay-pairing-journal-store.ts:37

export async function saveMobileRelayPairingJournal(
  journal: MobileRelayPairingJournal
): Promise<void> {
  requireNativeSecretStore()
  const metadata = MobileRelayPairingJournalMetadataSchema.parse(journal.metadata)
  const secrets = MobileRelayPairingJournalSecretsSchema.parse(journal.secrets)
  if (metadata.journalId !== secrets.journalId) {
    throw new Error('mobile relay pairing journal identity mismatch')
  }
  const mutation = journalMutation.then(async () => {
    const existingRaw = await AsyncStorage.getItem(JOURNAL_STORAGE_KEY)
    const existing = existingRaw ? parseMetadata(existingRaw) : null
    if (
      existing &&
      existing.journalId !== metadata.journalId &&
      (existing.winner !== undefined || existing.authorizationMode !== undefined)
    ) {
      throw new Error('mobile relay pairing recovery pending')
    }
    // Why: no install RPC can run before winner+authorization are durable, so
    // a new user-initiated scan may safely supersede a pre-authorization attempt.
    // Why: metadata-first makes a crash before the keychain write recover as
    // an incomplete journal, never as an untracked bearer secret.
    await AsyncStorage.setItem(JOURNAL_STORAGE_KEY, JSON.stringify(metadata))
    await writePairingKeychainItem(JOURNAL_SECRET_KEY, JSON.stringify(secrets))
  })
  journalMutation = mutation.catch(() => {})
  return mutation
}

export async function loadMobileRelayPairingJournal(): Promise<MobileRelayPairingJournal | null> {
  requireNativeSecretStore()
  const load = journalMutation.then(async () => {
    const rawMetadata = await AsyncStorage.getItem(JOURNAL_STORAGE_KEY)
    if (rawMetadata === null) {
      await deletePairingKeychainItem(JOURNAL_SECRET_KEY).catch(() => {})

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Call clearMobileRelayPairingJournal(existingJournalId) first to deliberately abandon the prior attempt.
  2. Block new pairing in the UI until the in-flight one resolves or is explicitly cancelled.
  3. Ensure only one pairing orchestrator is active at a time via a mutex/flag.

Example fix

// before
await saveMobileRelayPairingJournal(newJournal) // throws if old journal is mid-install
// after
const existing = await loadMobileRelayPairingJournal()
if (existing && (existing.metadata.winner || existing.metadata.authorizationMode)) {
  await clearMobileRelayPairingJournal(existing.metadata.journalId)
}
await saveMobileRelayPairingJournal(newJournal)
Defensive patterns

Strategy: validation

Validate before calling

import { loadMobileRelayPairingJournal, clearMobileRelayPairingJournal } from './mobile-relay-pairing-journal-store'
async function canSaveNewJournalSafely(): Promise<boolean> {
  const existing = await loadMobileRelayPairingJournal()
  if (!existing) return true
  return !(existing.metadata.winner !== undefined || existing.metadata.authorizationMode !== undefined)
}

Try / catch

try {
  await saveMobileRelayPairingJournal(newJournal)
} catch (error) {
  if (error instanceof Error && error.message === 'mobile relay pairing recovery pending') {
    // prompt user to abandon the in-flight attempt, then clear + retry
  }
}

Prevention

When it happens

Trigger: A second saveMobileRelayPairingJournal with a fresh journalId while a previous journal already recorded a winner or authorization mode (i.e. reached or passed the install-commit step).

Common situations: User started a new scan/pair while a previous attempt is mid-install; two pairing orchestrators racing; recovery flow still in progress when a new pair begins.

Related errors


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