stablyai/orca · error · Error

Codex reset attempt journal is unreadable

Error message

Codex reset attempt journal is unreadable

What it means

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.

Source

Thrown at mobile/src/storage/codex-reset-attempt-journal.ts:101

function stableAccountScopesEqual(
  left: CodexResetCreditExpectedScope,
  right: CodexResetCreditExpectedScope
): boolean {
  return (
    left.target.runtime === right.target.runtime &&
    left.target.wslDistro === right.target.wslDistro &&
    left.accountId === right.accountId &&
    left.accountRevision === right.accountRevision
  )
}

function parseAttempt(raw: string, identity: AttemptIdentity): CodexResetAttempt {
  let value: unknown
  try {
    value = JSON.parse(raw)
  } catch {
    throw new Error('Codex reset attempt journal is unreadable')
  }
  const result = CodexResetAttemptSchema.safeParse(value)
  if (
    !result.success ||
    result.data.hostId !== identity.hostId ||
    !stableAccountScopesEqual(result.data.expectedScope, identity.expectedScope)
  ) {
    throw new Error('Codex reset attempt journal is unreadable')
  }
  return result.data
}

async function withScopeMutation<T>(
  identity: AttemptIdentity,
  action: () => Promise<T>
): Promise<T> {
  const key = storageKey(identity)
  const previous = scopeMutations.get(key) ?? Promise.resolve()

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Clear the corrupt attempt key so a fresh idempotency key is generated on next reset (acceptable once the authoritative response is confirmed).
  2. Run `clearCodexResetAttemptAfterAuthoritativeResponse` only after confirming no in-flight reset RPC depends on the old key.
  3. Reproduce under the same AsyncStorage mock to find what wrote the non-JSON value.
  4. Ensure no other code path writes raw non-JSON under the `orca:codex-reset-credit-attempt:v1:` prefix.
Defensive patterns

Strategy: try-catch

Try / catch

// parseAttempt already throws; callers via getOrCreate/clear should treat an
// unreadable journal as 'start fresh' once no in-flight reset depends on it.
try {
  return parseAttempt(raw, identity)
} catch {
  // corrupt JSON — safe to overwrite with a new attempt if authoritative response confirmed
  await AsyncStorage.removeItem(key)
  return createFreshAttempt(identity)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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