{"record":{"id":"69d1d273d027eb43","repo":"stablyai/orca","slug":"stale-mobile-relay-pairing-journal","errorCode":null,"errorMessage":"stale mobile relay pairing journal","messagePattern":"stale mobile relay pairing journal","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"mobile/src/transport/mobile-relay-pairing-journal-store.ts","lineNumber":99,"sourceCode":"  return load\n}\n\nasync function removeIncompleteJournal(): Promise<void> {\n  // Why: metadata is the discoverable cleanup pointer; remove it before the\n  // native secret so a second crash can only leave a self-cleaning orphan.\n  await AsyncStorage.removeItem(JOURNAL_STORAGE_KEY)\n  await deletePairingKeychainItem(JOURNAL_SECRET_KEY).catch(() => {})\n}\n\nexport async function updateMobileRelayPairingJournal(\n  journalId: string,\n  update: (metadata: MobileRelayPairingJournalMetadata) => MobileRelayPairingJournalMetadata\n): Promise<void> {\n  const mutation = journalMutation.then(async () => {\n    const raw = await AsyncStorage.getItem(JOURNAL_STORAGE_KEY)\n    const current = raw ? parseMetadata(raw) : null\n    if (!current || current.journalId !== journalId) {\n      throw new Error('stale mobile relay pairing journal')\n    }\n    const next = MobileRelayPairingJournalMetadataSchema.parse(update(current))\n    if (next.journalId !== journalId) {\n      throw new Error('mobile relay pairing journal identity mismatch')\n    }\n    await AsyncStorage.setItem(JOURNAL_STORAGE_KEY, JSON.stringify(next))\n  })\n  journalMutation = mutation.catch(() => {})\n  return mutation\n}\n\nexport async function clearMobileRelayPairingJournal(journalId: string): Promise<void> {\n  const mutation = journalMutation.then(async () => {\n    const raw = await AsyncStorage.getItem(JOURNAL_STORAGE_KEY)\n    const current = raw ? parseMetadata(raw) : null\n    if (current && current.journalId !== journalId) {\n      throw new Error('stale mobile relay pairing journal')\n    }","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/mobile/src/transport/mobile-relay-pairing-journal-store.ts#L81-L117","documentation":"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.","triggerScenarios":"Calling updateMobileRelayPairingJournal after the journal was cleared, or with a journalId from a previous attempt that has since been replaced.","commonSituations":"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.","solutions":["Load the current journal via loadMobileRelayPairingJournal before issuing updates and use its journalId.","Treat this error as a no-op/cancel for the stale callback rather than a hard failure.","Ensure update callers always hold a journalId obtained from a fresh load, not a cached one."],"exampleFix":"// before\nawait updateMobileRelayPairingJournal(staleJournalId, setWinner)\n// after\nconst current = await loadMobileRelayPairingJournal()\nif (!current || current.metadata.journalId !== staleJournalId) return // stale, ignore\nawait updateMobileRelayPairingJournal(current.metadata.journalId, setWinner)","handlingStrategy":"validation","validationCode":"import { loadMobileRelayPairingJournal } from './mobile-relay-pairing-journal-store'\nasync function currentJournalIdOrNull(): Promise<string | null> {\n  const current = await loadMobileRelayPairingJournal()\n  return current ? current.metadata.journalId : null\n}\n// only call updateMobileRelayPairingJournal when this returns the id you hold","typeGuard":null,"tryCatchPattern":"try {\n  await updateMobileRelayPairingJournal(journalId, setWinner)\n} catch (error) {\n  if (error instanceof Error && error.message === 'stale mobile relay pairing journal') {\n    // stale callback: the journal moved on; treat as no-op\n  }\n}","preventionTips":["Always load the current journal immediately before issuing an update and use the returned journalId.","Design late RPC callbacks to tolerate a missing/stale journal as a cancel, not an error.","Never cache journalId across async gaps that may include a user restart."],"tags":["relay","pairing","storage","state-machine","stale"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}