stablyai/orca · error · Error

Codex reset attempt journal identity changed

Error message

Codex reset attempt journal identity changed

What it means

Thrown by `clearCodexResetAttemptAfterAuthoritativeResponse` when the stored attempt's `idempotencyKey` does not equal the `identity.idempotencyKey` passed in. The clear is meant to run only after the authoritative provider response confirms a specific attempt completed; a mismatch means a different/older attempt is in the journal (a newer reset started, or the wrong key was supplied) and clearing it would delete state still needed for in-flight idempotency.

Source

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

    // Why: the key must survive a committed provider mutation whose response is
    // lost; no reset RPC may start until this write has completed successfully.
    await AsyncStorage.setItem(key, JSON.stringify(attempt))
    return attempt
  })
}

export async function clearCodexResetAttemptAfterAuthoritativeResponse(
  identity: AttemptIdentity & { idempotencyKey: string }
): Promise<void> {
  return withScopeMutation(identity, async () => {
    const key = storageKey(identity)
    const raw = await AsyncStorage.getItem(key)
    if (raw === null) {
      return
    }
    const current = parseAttempt(raw, identity)
    if (current.idempotencyKey !== identity.idempotencyKey) {
      throw new Error('Codex reset attempt journal identity changed')
    }
    await AsyncStorage.removeItem(key)
  })
}

/** Test-only: drain in-memory queues while preserving the durable storage mock. */
export function resetCodexResetAttemptJournalForTests(): void {
  scopeMutations.clear()
}

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Pass the exact `idempotencyKey` returned by `getOrCreateCodexResetAttempt` that corresponds to the response being confirmed.
  2. If a new reset superseded the old one, do not clear — the new attempt still needs its journal entry.
  3. Ensure only one reset flow is in flight per account scope at a time (the `withScopeMutation` queue serializes, but distinct callers should coordinate keys).
  4. Capture the idempotency key at attempt-creation time and thread the same value through to the clear call.
Defensive patterns

Strategy: validation

Validate before calling

// Thread the exact key from creation through to clear.
const attempt = await getOrCreateCodexResetAttempt(identity)
// ...perform reset with attempt.idempotencyKey...
await clearCodexResetAttemptAfterAuthoritativeResponse({ ...identity, idempotencyKey: attempt.idempotencyKey })

Try / catch

try {
  await clearCodexResetAttemptAfterAuthoritativeResponse({ ...identity, idempotencyKey })
} catch (err) {
  if (err instanceof Error && err.message === 'Codex reset attempt journal identity changed') {
    // a newer attempt superseded this one — do not clear; leave journal intact
  } else throw err
}

Prevention

When it happens

Trigger: Calling clear with an `idempotencyKey` that differs from the one persisted: a second reset created a new attempt (new key) between the original reset and this clear, or the caller is clearing with a stale/incorrect key.

Common situations: User retried the reset before the first response arrived, generating a new idempotency key; the clear callback fired with an old captured key after a key rotation; concurrent reset attempts racing through the serialized `withScopeMutation` queue.

Related errors


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