stablyai/orca · error
mobile relay pairing journal identity mismatch
Error message
mobile relay pairing journal identity mismatch
What it means
Thrown in saveMobileRelayPairingJournal when metadata.journalId !== secrets.journalId after both halves parse. Metadata (AsyncStorage) and secrets (native keychain) must share one journalId since they are persisted to different stores and rejoined on load.
Source
Thrown at mobile/src/transport/mobile-relay-pairing-journal-store.ts:27
import {
deletePairingKeychainItem,
readPairingKeychainItem,
resetPairingKeychainForTests,
writePairingKeychainItem
} from './pairing-keychain'
const JOURNAL_STORAGE_KEY = 'orca:mobile-relay:pairing-journal:v1'
const JOURNAL_SECRET_KEY = 'orca.mobile-relay.pairing-journal.v1'
let journalMutation: Promise<void> = Promise.resolve()
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))
})View on GitHub (pinned to 1136503c6a)
Solutions
- Always construct journals via createMobileRelayPairingJournal, which derives one journalId and emits both halves.
- If assembling manually, generate journalId once and inject the same value into both metadata and secrets.
Example fix
// before
const journal = {
metadata: { ...prevMetadata, journalId: 'pair-A' },
secrets: { ...freshSecrets, journalId: 'pair-B' } // mismatch
}
await saveMobileRelayPairingJournal(journal)
// after
const journal = createMobileRelayPairingJournal({ offer, hostId, hostName })
await saveMobileRelayPairingJournal(journal) Defensive patterns
Strategy: validation
Validate before calling
function assertJournalIdsMatch(journal: MobileRelayPairingJournal): void {
if (journal.metadata.journalId !== journal.secrets.journalId) {
throw new Error('programmer error: journal halves have divergent ids')
}
} Prevention
- Construct journals only via createMobileRelayPairingJournal so a single journalId seeds both halves.
- If you must assemble manually, derive journalId once and inject into both metadata and secrets.
- Add a unit test asserting metadata.journalId === secrets.journalId for any fixture.
When it happens
Trigger: The caller assembled a MobileRelayPairingJournal from mismatched halves: metadata from one pairing attempt and secrets from another, or a hand-built journal with divergent ids.
Common situations: A bug in the caller constructing the journal from stale cached pieces; test fixtures with copy-paste journalId errors; reusing a metadata object but regenerating secrets.
Related errors
- mobile relay host overlay storage unreadable
- mobile relay pairing recovery pending
- stale mobile relay pairing journal
- relay credential install result does not match pairing journ
- Orca Relay upgrade state requires a native secret store
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/22296300b5d5147d.
Report an issue: GitHub.