{"record":{"id":"9197714ac32dfa86","repo":"stablyai/orca","slug":"codex-reset-attempt-journal-is-unreadable","errorCode":null,"errorMessage":"Codex reset attempt journal is unreadable","messagePattern":"Codex reset attempt journal is unreadable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"mobile/src/storage/codex-reset-attempt-journal.ts","lineNumber":101,"sourceCode":"\nfunction stableAccountScopesEqual(\n  left: CodexResetCreditExpectedScope,\n  right: CodexResetCreditExpectedScope\n): boolean {\n  return (\n    left.target.runtime === right.target.runtime &&\n    left.target.wslDistro === right.target.wslDistro &&\n    left.accountId === right.accountId &&\n    left.accountRevision === right.accountRevision\n  )\n}\n\nfunction parseAttempt(raw: string, identity: AttemptIdentity): CodexResetAttempt {\n  let value: unknown\n  try {\n    value = JSON.parse(raw)\n  } catch {\n    throw new Error('Codex reset attempt journal is unreadable')\n  }\n  const result = CodexResetAttemptSchema.safeParse(value)\n  if (\n    !result.success ||\n    result.data.hostId !== identity.hostId ||\n    !stableAccountScopesEqual(result.data.expectedScope, identity.expectedScope)\n  ) {\n    throw new Error('Codex reset attempt journal is unreadable')\n  }\n  return result.data\n}\n\nasync function withScopeMutation<T>(\n  identity: AttemptIdentity,\n  action: () => Promise<T>\n): Promise<T> {\n  const key = storageKey(identity)\n  const previous = scopeMutations.get(key) ?? Promise.resolve()","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/mobile/src/storage/codex-reset-attempt-journal.ts#L83-L119","documentation":"Thrown by `parseAttempt` in the codex-reset-credit attempt journal when `JSON.parse(raw)` throws — the persisted AsyncStorage value under the attempt key is not valid JSON. The journal stores one durable reset attempt per stable account scope so a lost provider-mutation response can be made idempotent; a corrupt entry cannot be trusted and is rejected wholesale.","triggerScenarios":"Reading the codex-reset attempt key (`orca:codex-reset-credit-attempt:v1:<digest>`) where AsyncStorage returns a string that is not parseable JSON — partial write, external tampering, or storage corruption from a crash mid-write.","commonSituations":"App was killed mid-`setItem` leaving a truncated string; a storage migration wrote a non-JSON value under the same key prefix; AsyncStorage backend (SQLite/file) corruption on a specific OS version; manual debug writes.","solutions":["Clear the corrupt attempt key so a fresh idempotency key is generated on next reset (acceptable once the authoritative response is confirmed).","Run `clearCodexResetAttemptAfterAuthoritativeResponse` only after confirming no in-flight reset RPC depends on the old key.","Reproduce under the same AsyncStorage mock to find what wrote the non-JSON value.","Ensure no other code path writes raw non-JSON under the `orca:codex-reset-credit-attempt:v1:` prefix."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// parseAttempt already throws; callers via getOrCreate/clear should treat an\n// unreadable journal as 'start fresh' once no in-flight reset depends on it.\ntry {\n  return parseAttempt(raw, identity)\n} catch {\n  // corrupt JSON — safe to overwrite with a new attempt if authoritative response confirmed\n  await AsyncStorage.removeItem(key)\n  return createFreshAttempt(identity)\n}","preventionTips":["Write the journal atomically (the code already serialises via withScopeMutation).","Never write non-JSON under the orca:codex-reset-credit-attempt:v1: prefix.","On schema/version changes, bump the prefix version so corrupt/old entries are ignored."],"tags":["storage","async-storage","json","corruption","idempotency"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}