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
- Call clearMobileRelayPairingJournal(existingJournalId) first to deliberately abandon the prior attempt.
- Block new pairing in the UI until the in-flight one resolves or is explicitly cancelled.
- 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
- Serialize pairing orchestrators with a single in-flight flag so a second attempt cannot race a committed one.
- Before saving a new journal, load the existing one and clear it if it is past the authorization commit.
- Reflect 'pairing in progress' in the UI so users cannot start a concurrent pair.
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
- stale mobile relay pairing journal
- mobile relay pairing journal identity mismatch
- relay credential resolved as an unexpected credential kind
- relay pairing client closed
- Codex reset attempt journal identity changed
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/59588dbcd4f9607f.
Report an issue: GitHub.