{"record":{"id":"53990aa1919aabd5","repo":"stablyai/orca","slug":"host-list-storage-unreadable","errorCode":null,"errorMessage":"host list storage unreadable","messagePattern":"host list storage unreadable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"mobile/src/transport/host-metadata-store.ts","lineNumber":19,"sourceCode":"import AsyncStorage from '@react-native-async-storage/async-storage'\nimport { StoredHostProfileSchema, type HostProfile, type StoredHostProfile } from './types'\n\nconst STORAGE_KEY = 'orca:hosts'\n\nexport async function loadStoredHostProfiles(): Promise<StoredHostProfile[] | null> {\n  return parseStoredHostProfiles(await AsyncStorage.getItem(STORAGE_KEY))\n}\n\nexport async function readStoredHostProfilesForMutation(): Promise<StoredHostProfile[]> {\n  try {\n    const parsed = await loadStoredHostProfiles()\n    if (parsed) {\n      return parsed\n    }\n  } catch {\n    // Normalize storage and payload failures for fail-closed mutations.\n  }\n  throw new Error('host list storage unreadable')\n}\n\nexport function writeStoredHostProfiles(hosts: readonly StoredHostProfile[]): Promise<void> {\n  return AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(hosts))\n}\n\nexport function toStoredHostProfile(host: HostProfile): StoredHostProfile {\n  const { id, name, endpoint, publicKeyB64, lastConnected } = host\n  return { id, name, endpoint, publicKeyB64, lastConnected }\n}\n\nfunction parseStoredHostProfiles(raw: string | null): StoredHostProfile[] | null {\n  if (!raw) {\n    return []\n  }\n  try {\n    const parsed = JSON.parse(raw) as unknown\n    if (!Array.isArray(parsed)) {","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/mobile/src/transport/host-metadata-store.ts#L1-L37","documentation":"readStoredHostProfilesForMutation could not load a usable host list from AsyncStorage (key 'orca:hosts'). The function tries loadStoredHostProfiles and throws only if parsing returns null (corrupt/non-array payload) or the AsyncStorage read itself throws. This is a fail-closed guard: mutations (save/remove/rename) must not proceed over unreadable storage because a blind write could wipe all paired hosts.","triggerScenarios":"AsyncStorage.getItem throws (native I/O failure); parseStoredHostProfiles returns null because JSON.parse failed or the top-level value is not an array; the stored payload is valid JSON but all entries failed StoredHostProfileSchema validation (all pre-v0.0.3 records with deviceToken).","commonSituations":"App crashed mid-write leaving truncated JSON; all stored host records are from a pre-v0.0.3 format that embedded deviceToken (filtered out, yielding null only if the entire array was dropped); AsyncStorage corruption after OS update; test environment without proper AsyncStorage.","solutions":["Do NOT suppress and proceed — that wipes the host list. Route to a recovery UI that offers re-pairing.","If the payload is corrupt, offer to reset 'orca:hosts' to [] after the user confirms, since hosts can be re-paired.","Check device storage space and AsyncStorage health before retrying.","Ensure at least one host record passes StoredHostProfileSchema (pre-0.0.3 records with deviceToken are intentionally filtered)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"async function canReadHostList(): Promise<boolean> {\n  try {\n    const hosts = await loadStoredHostProfiles()\n    return hosts !== null\n  } catch {\n    return false\n  }\n}\n\n// Check before mutations\nif (!(await canReadHostList())) {\n  showStorageRecoveryUI()\n}","typeGuard":null,"tryCatchPattern":"try {\n  await saveHost(host)\n} catch (e) {\n  if (e.message === 'host list storage unreadable') {\n    showStorageRecoveryUI()\n  }\n}","preventionTips":["Never proceed with a mutation over unreadable storage — it wipes paired hosts.","Route to a recovery UI that offers re-pairing when storage is corrupt.","Check AsyncStorage health and device storage space.","Offer to reset 'orca:hosts' to [] only after the user confirms (hosts can be re-paired)."],"tags":["async-storage","host-profiles","data-integrity","mobile","fail-closed"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}