stablyai/orca · error

mobile relay host overlay storage unreadable

Error message

mobile relay host overlay storage unreadable

What it means

Thrown by readOverlaysForMutation when parseOverlays returns null: AsyncStorage holds a value under 'orca:mobile-relay:host-overlays:v2' that is not a JSON array (corrupt JSON or a non-array shape). The guard deliberately refuses to rewrite an unreadable namespace as an empty list, to avoid destroying relay recovery data during an unrelated mutation.

Source

Thrown at mobile/src/transport/mobile-relay-host-overlay-store.ts:33

    const value = JSON.parse(raw) as unknown
    if (!Array.isArray(value)) {
      return null
    }
    return value.flatMap((item) => {
      const result = MobileRelayHostOverlaySchema.safeParse(item)
      return result.success ? [result.data] : []
    })
  } catch {
    return null
  }
}

async function readOverlaysForMutation(): Promise<MobileRelayHostOverlay[]> {
  const overlays = parseOverlays(await AsyncStorage.getItem(OVERLAY_STORAGE_KEY))
  if (!overlays) {
    // Why: never rewrite an unreadable v2 namespace as an empty list; doing so
    // would destroy relay recovery data during an unrelated host mutation.
    throw new Error('mobile relay host overlay storage unreadable')
  }
  return overlays
}

async function mutateOverlays(
  update: (overlays: MobileRelayHostOverlay[]) => MobileRelayHostOverlay[]
): Promise<void> {
  const mutation = overlayMutation.then(async () => {
    const current = await readOverlaysForMutation()
    const next = update(current)
    // Why: direct-only saves commonly have no overlay to remove; avoid a full
    // AsyncStorage write when cleanup leaves the durable list unchanged.
    if (next !== current) {
      await AsyncStorage.setItem(OVERLAY_STORAGE_KEY, JSON.stringify(next))
    }
  })
  overlayMutation = mutation.catch(() => {})
  return mutation

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Read the raw value under the key (read-only) to confirm it is non-array/corrupt.
  2. If genuinely unrecoverable, explicitly remove the key once, accepting that relay overlays will rebuild from host discovery.
  3. Ensure every writer routes through MobileRelayHostOverlaySchema.parse so only valid v2 arrays are stored.
  4. Add a one-time repair migration that detects non-array JSON and either quarantines or clears it deliberately.
Defensive patterns

Strategy: try-catch

Validate before calling

import AsyncStorage from '@react-native-async-storage/async-storage'
async function isOverlayStorageReadable(): Promise<boolean> {
  const raw = await AsyncStorage.getItem('orca:mobile-relay:host-overlays:v2')
  if (raw === null) return true
  try {
    return Array.isArray(JSON.parse(raw))
  } catch {
    return false
  }
}

Try / catch

try {
  await saveMobileRelayHostOverlay(overlay)
} catch (error) {
  if (error instanceof Error && error.message === 'mobile relay host overlay storage unreadable') {
    // deliberate one-time repair: quarantine the bad key, then retry
  }
}

Prevention

When it happens

Trigger: Any call to saveMobileRelayHostOverlay or removeMobileRelayHostOverlay(s) while the v2 key contains non-array JSON: a string, an object, or truncated JSON from a crash mid-write.

Common situations: A previous app version wrote a non-array under the same key; AsyncStorage corruption from a crash during setItem; manual injection during testing; a partial migration left legacy data in the v2 namespace.

Related errors


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