stablyai/orca · error
pairing keychain presence record is invalid
Error message
pairing keychain presence record is invalid
What it means
Thrown by parsePresenceGeneration when reading the per-key 'presence' marker from Android AsyncStorage during pairing-keychain generation rotation. The stored raw string must be a canonical non-negative integer (Number.isInteger, 0..MAX_GENERATION=8) AND round-trip exactly (String(parsed) === raw), so values like '08', '1e0', ' ', or '-1' are all rejected. Unlike parseGeneration (which degrades gracefully to generation 0), the presence path throws because a corrupt presence record cannot be safely interpreted — it is consulted only on Android to detect whether a specific generation still holds a durable credential.
Source
Thrown at mobile/src/transport/pairing-keychain.ts:77
reliable: false,
error: new Error('pairing keychain generation record is invalid')
}
}
return { generation: parsed, pending, reliable: true }
}
function parsePresenceGeneration(raw: string | null): number | null {
if (raw === null) {
return null
}
const parsed = Number(raw)
if (
!Number.isInteger(parsed) ||
parsed < 0 ||
parsed > MAX_GENERATION ||
String(parsed) !== raw
) {
throw new Error('pairing keychain presence record is invalid')
}
return parsed
}
function presenceStorageKey(key: string): string {
return `${PRESENCE_STORAGE_PREFIX}${key}`
}
async function loadPresenceGeneration(key: string): Promise<number | null> {
if (Platform.OS !== 'android') {
return null
}
return parsePresenceGeneration(await AsyncStorage.getItem(presenceStorageKey(key)))
}
async function loadGeneration(): Promise<LoadedGeneration> {
if (cachedGeneration !== null) {
return { ...cachedGeneration, reliable: true }View on GitHub (pinned to 1136503c6a)
Solutions
- Clear the offending presence key (AsyncStorage.removeItem with the 'orca:pairing-keychain-presence:<key>' prefix) and let the keychain reseed generation 0 on next pairing.
- Inspect every presence value via AsyncStorage.getAllKeys and filter the PRESENCE_STORAGE_PREFIX; rewrite any non-canonical entry with String(Number(raw)).
- If hit during a migration, write a one-time normalizer that canonicalizes all presence keys to String(parsed) before parsePresenceGeneration runs.
- Reproduce with a debug build that logs the raw value in parsePresenceGeneration before throwing, to identify which key is corrupt.
Example fix
// before (writes non-canonical value) await AsyncStorage.setItem(presenceStorageKey(key), String(0o1)) // '1' is fine but '0o1' is not // after const gen = 1 await AsyncStorage.setItem(presenceStorageKey(key), String(gen)) // canonical: '1'
Defensive patterns
Strategy: validation
Validate before calling
// Validate before parsePresenceGeneration runs
function isValidPresenceRecord(raw: string | null): boolean {
if (raw === null) return true
const parsed = Number(raw)
return Number.isInteger(parsed) && parsed >= 0 && parsed <= 8 && String(parsed) === raw
}
// usage: const raw = await AsyncStorage.getItem(presenceStorageKey(key))
// if (!isValidPresenceRecord(raw)) await AsyncStorage.removeItem(presenceStorageKey(key)) Type guard
function isCanonicalPresenceGeneration(raw: unknown): raw is string {
return typeof raw === 'string'
&& raw !== ''
&& Number.isInteger(Number(raw))
&& Number(raw) >= 0
&& Number(raw) <= 8
&& String(Number(raw)) === raw
} Try / catch
try {
const gen = await loadPresenceGeneration(key)
} catch (error) {
if (error instanceof Error && error.message === 'pairing keychain presence record is invalid') {
await AsyncStorage.removeItem(presenceStorageKey(key)) // clear corrupt record
return null // treat as absent
}
throw error
} Prevention
- Always write presence values via commitGeneration-style helpers that emit String(parsed), never template literals or float casts.
- Add a startup migration that canonicalizes every PRESENCE_STORAGE_PREFIX key before parsePresenceGeneration can run.
- In tests, seed AsyncStorage with String(n) literals, not JSON.stringify(8) (which yields '8' but is a footgun for floats).
When it happens
Trigger: loadPresenceGeneration(key) on Android reads a value under 'orca:pairing-keychain-presence:<key>' that is non-null but fails the canonical-integer check. Caused by: external tampering with AsyncStorage, a partial write that left a malformed value, a downgrade from a future schema, or a debugging tool that wrote a float/string into the presence slot.
Common situations: Android-only: iOS short-circuits to null at line 87. Hits developers inspecting/migrating AsyncStorage during pairing-keychain debugging, after a schema change that didn't migrate presence keys, or when a test fixture seeds the storage with a non-canonical integer literal.
Related errors
- pending host credential cleanup storage unreadable
- mobile relay pairing journal identity mismatch
- relay credential install was not authoritatively reconciled
- Missing signing identity for Orca Computer Use helper app
- Missing signing identity for orca-notification-status helper
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/201b4aac8c24375e.
Report an issue: GitHub.