stablyai/orca · warning

stale mobile relay pairing journal

Error message

stale mobile relay pairing journal

What it means

Thrown in updateMobileRelayPairingJournal when there is no stored metadata (current === null) or its journalId does not equal the journalId argument. The update targets a journal that no longer exists or was superseded.

Source

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

  return load
}

async function removeIncompleteJournal(): Promise<void> {
  // Why: metadata is the discoverable cleanup pointer; remove it before the
  // native secret so a second crash can only leave a self-cleaning orphan.
  await AsyncStorage.removeItem(JOURNAL_STORAGE_KEY)
  await deletePairingKeychainItem(JOURNAL_SECRET_KEY).catch(() => {})
}

export async function updateMobileRelayPairingJournal(
  journalId: string,
  update: (metadata: MobileRelayPairingJournalMetadata) => MobileRelayPairingJournalMetadata
): Promise<void> {
  const mutation = journalMutation.then(async () => {
    const raw = await AsyncStorage.getItem(JOURNAL_STORAGE_KEY)
    const current = raw ? parseMetadata(raw) : null
    if (!current || current.journalId !== journalId) {
      throw new Error('stale mobile relay pairing journal')
    }
    const next = MobileRelayPairingJournalMetadataSchema.parse(update(current))
    if (next.journalId !== journalId) {
      throw new Error('mobile relay pairing journal identity mismatch')
    }
    await AsyncStorage.setItem(JOURNAL_STORAGE_KEY, JSON.stringify(next))
  })
  journalMutation = mutation.catch(() => {})
  return mutation
}

export async function clearMobileRelayPairingJournal(journalId: string): Promise<void> {
  const mutation = journalMutation.then(async () => {
    const raw = await AsyncStorage.getItem(JOURNAL_STORAGE_KEY)
    const current = raw ? parseMetadata(raw) : null
    if (current && current.journalId !== journalId) {
      throw new Error('stale mobile relay pairing journal')
    }

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Load the current journal via loadMobileRelayPairingJournal before issuing updates and use its journalId.
  2. Treat this error as a no-op/cancel for the stale callback rather than a hard failure.
  3. Ensure update callers always hold a journalId obtained from a fresh load, not a cached one.

Example fix

// before
await updateMobileRelayPairingJournal(staleJournalId, setWinner)
// after
const current = await loadMobileRelayPairingJournal()
if (!current || current.metadata.journalId !== staleJournalId) return // stale, ignore
await updateMobileRelayPairingJournal(current.metadata.journalId, setWinner)
Defensive patterns

Strategy: validation

Validate before calling

import { loadMobileRelayPairingJournal } from './mobile-relay-pairing-journal-store'
async function currentJournalIdOrNull(): Promise<string | null> {
  const current = await loadMobileRelayPairingJournal()
  return current ? current.metadata.journalId : null
}
// only call updateMobileRelayPairingJournal when this returns the id you hold

Try / catch

try {
  await updateMobileRelayPairingJournal(journalId, setWinner)
} catch (error) {
  if (error instanceof Error && error.message === 'stale mobile relay pairing journal') {
    // stale callback: the journal moved on; treat as no-op
  }
}

Prevention

When it happens

Trigger: Calling updateMobileRelayPairingJournal after the journal was cleared, or with a journalId from a previous attempt that has since been replaced.

Common situations: A late install/resume-confirm RPC callback arriving after the user restarted pairing and the journal was replaced; recovery cleanup removed the journal between dispatch and callback.

Related errors


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