Skip to content

fix(sync): accept legacy Codex account identity metadata - #1196

Merged
robinebers merged 1 commit into
mainfrom
codex/fix-legacy-codex-icloud-identity
Sep 2, 2026
Merged

fix(sync): accept legacy Codex account identity metadata#1196
robinebers merged 1 commit into
mainfrom
codex/fix-legacy-codex-icloud-identity

Conversation

@robinebers

@robinebers robinebers commented Sep 1, 2026

Copy link
Copy Markdown
Owner

TL;DR

Restore compatibility with older iCloud history files that include a Codex account ID. Valid history from another Mac no longer gets rejected merely because it contains this extra metadata.

What was happening

  • Earlier v2 sync writers included both Claude and Codex account identities.
  • The current reader only accepted Claude identities, so a valid Codex identity caused the entire device file to be skipped.
  • Settings showed a generic unreadable-data warning, while the log misleadingly reported an invalid Claude account identity.

What this changes

  • Accept the legacy bare codex identity in v2 documents, while still requiring its provider history and a valid identity value.
  • Scope duplicate identity checks by provider family, so identical opaque IDs in Claude and Codex do not collide.
  • Cover legacy document decoding, malformed and orphaned Codex identities, and aggregation with legacy Codex metadata.
  • Document compatibility with these older files.

Heads-up

  • Codex usage remains synced as before; this does not introduce Codex account-based matching or change the writer format.
  • No synced files are deleted or rewritten by the compatibility fix. Claude account matching and its duplicate checks remain intact.
  • This is a reader-only logic fix, with no UI layout or copy changes.

Tests

  • Confirmed the new regression test failed before the fix with invalidIdentity("codex").
  • swift test --filter 'UsageHistoryDocumentTests|UsageHistoryAggregatorTests|ICloudUsageSyncStoreTests': 23 tests passed. The local Xcode beta test runner needed a symlink to the existing Sparkle framework under its ignored build-output directory.
  • CONFIG=debug ./script/build_and_run.sh verify: rebuilt, signed, and relaunched successfully.
  • With sync enabled, confirmed this Mac wrote fresh history and no new iCloud read errors appeared after restart. Visual inspection was unavailable because the UI tool timed out.
  • git diff --check passed.

Note

Low Risk
Reader-only validation tweak for synced history documents; Claude account matching and duplicate rules within Claude are unchanged.

Overview
Restores iCloud compatibility for v2 history files that still carry a bare codex entry in identities from older writers. Those peers were failing UsageHistoryDocument.validate() and being dropped entirely in ICloudUsageSyncStore, even when Claude/Codex usage was otherwise valid.

validate() now treats codex identities as allowed metadata on openusage.history.v2 (still requiring matching providers["codex"] and the same identity format rules as Claude). Duplicate identity detection keys on providerFamily:identity instead of identity alone, so the same opaque string on Claude and Codex no longer counts as a duplicate.

Tests cover decode/validate edge cases (malformed or orphaned Codex identities) and aggregation with legacy Codex metadata; docs note that the extra Codex ID is readable but does not change Codex merge behavior.

Reviewed by Cursor Bugbot for commit 17e2745. Bugbot is set up for automated code reviews on this repo. Configure here.

@robinebers
robinebers merged commit b15fd6c into main Sep 2, 2026
6 checks passed
@robinebers
robinebers deleted the codex/fix-legacy-codex-icloud-identity branch September 2, 2026 09:38
mstallone added a commit to mstallone/runway that referenced this pull request Sep 5, 2026
## TL;DR

Selective re-implementation of the OpenUsage commits since Runway #111
that still apply: Claude Desktop's account-prefixed token caches, Claude
spend tiles without an OAuth login, Grok subagent session ledgers, Codex
Business Premium, Cursor's Models/Other Models labels, and new model
rates.

## What was happening

- Upstream has moved on since #111. Each new OpenUsage commit was
reviewed against Runway's architecture, existing ports, and the "don't
bloat" bar.
- Recent Claude Desktop builds store tokens under `acct:<user>|<legacy
key>` (openusage robinebers#1212). Runway expected the client UUID first and
skipped those entries, so a Desktop-only login showed Not logged in.
- Claude returned a hard authentication error before scanning local logs
when no OAuth login existed (openusage robinebers#1138), so API-key gateway users
lost Today/Yesterday/Last 30 Days.
- Grok's scanner skipped every `subagent*` session (openusage robinebers#1193).
Child work that the coordinator turn did not include disappeared from
spend.
- Codex's `self_serve_business_prolite` entitlement rendered as "Self
Serve Business Prolite" (openusage robinebers#1194).
- Cursor's dashboard now calls the two model pools **Cursor Models** and
**Other Models**; Runway still said Auto Usage / API Usage (openusage
robinebers#1134, labels only).
- GPT-6 Astra, Gemini 3.8 Flash, Fable 5.1, GLM 5.3, and Grok Bot CSV
slugs had no supplement entries, so those rows tripped the
unpriced-model warning.

## What this changes

- Desktop cache selection strips the `acct:<user>|` prefix, keeps only
the signed-in account (from `lastKnownAccountUuid`), and lets a scoped
tombstone suppress the matching legacy V1 alias.
- Unauthenticated Claude refreshes still scan local logs. Spend tiles
render under the existing Not logged in notice when those logs contain
usage; an empty machine stays a hard error card.
- Grok scans every durable `updates.jsonl` ledger. Prompt-id dedup still
drops forked parent replays. `summary.json` is no longer required to
keep a ledger.
- Codex maps `self_serve_business_prolite` to **Business Premium**.
- Cursor widget IDs are unchanged. Titles/labels become Cursor Models /
Other Models to match Cursor's dashboard.
- Pricing supplement: GPT-6 Astra (OpenAI card, 2× fast), Gemini 3.8
Flash (Cursor table, $3.50 output), Fable 5.1, GLM 5.3, and `grok-bot-*`
→ Grok 4.6.

## Heads-up

Reviewed and **not** ported, with reasons:

- **openusage robinebers#1116 / robinebers#1127 / robinebers#1185** (analytics ping, PostHog) — Runway
removed analytics in #9.
- **openusage robinebers#1111 / robinebers#1136 / robinebers#1106** (scroll / Settings lag / SVG
parse) — Runway already has `ReorderFrameStore`, parsed-once
`ProviderMark`, and the rebuilt popover path. Taking their patch would
duplicate that work.
- **openusage robinebers#1137 / robinebers#1165 / robinebers#1141** (Codex Session default, Fable
order) — Runway already hides Codex Session by default and already
places Fable directly below Weekly. Layout defaults stay an owner
decision.
- **openusage robinebers#1134 Grok Bot meter** — new Cursor metric. AGENTS.md
requires owner confirmation of the four defaults before adding it; this
PR only takes the dashboard label rename and the `grok-bot-*` pricing
aliases.
- **openusage robinebers#1139** (Antigravity local spend) — new scanner, protobuf
decoder, and new metrics. Too large for this wave and needs the same
default-placement call.
- **openusage robinebers#1195** (OpenCode Codex OAuth attribution) — new scanner
sharing Codex request pricing. Real feature, own follow-up; folding it
in here would bloat the PR.
- **openusage robinebers#1164** (Claude multi-account) — Runway already discovers
Claude homes and gives each account its own card.
- **openusage robinebers#1177** (Codex fallback pricing Settings) — extra Settings
surface; earlier port waves skipped extra reset/settings chrome for the
same reason.
- **openusage robinebers#1179** (dead pin ID remap) — OpenUsage layout keys and
old Antigravity IDs. Runway installs never held those keys (different
defaults domain), and schema v3/v4 are already used for the beta-channel
and telemetry retirements.
- **openusage robinebers#1172** (bound log memory) — Runway already rejects
non-finite / overflowing token counts at the parse boundary instead of
clamping them.
- **openusage robinebers#1167 / robinebers#1016** (sub-1% "Not started", untouched pacing) —
already in Runway (`used <= 0`, `Pace.evaluate` returns nil when
unused).
- **openusage robinebers#1128** (Sparkle 2.9.6) — still a relevant bump; leaving
it to Dependabot rather than mixing a package-resolution change into
this accuracy PR.
- **openusage robinebers#1170 / robinebers#1159 / robinebers#1143 / robinebers#1163** (contribution policy,
screenshot assets, test-suite cleanup) — not user-facing on Runway, or
would churn tests without changing behavior.
- **openusage robinebers#1196** (legacy Codex iCloud identity) — Runway's sync
identity path is already fork-specific.

Gemini 3.8 Flash output is **$3.50**, from [Cursor's
table](https://cursor.com/docs/models-and-pricing.md), not upstream's
$3.75 Google API rate. That matches how Runway priced Gemini 3.7.

## Tests

- Desktop: prefixed key for the signed-in account wins; foreign `acct:`
keys are ignored; a scoped V2 tombstone suppresses the V1 alias;
`load()` reads `lastKnownAccountUuid`.
- Claude: no credentials plus local logs → spend tiles and Not logged
in, not an error card. Empty machine still errors.
- Grok: subagent ledger is included; fork replay of a shared prompt
still counts once.
- Codex: `self_serve_business_prolite` → Business Premium, weekly-only
window.
- Pricing: Astra / 3.8 Flash / Fable 5.1 / GLM 5.3 / grok-bot slugs and
router labels resolve. `testEveryAliasCanonicalResolves` covers the new
rules.
- Cursor mapper tests updated to the new labels; widget IDs unchanged.

`swift test --filter
"ClaudeDesktopAuthStoreTests|ClaudeProviderTests|CursorProviderTests|CursorUsageSummaryTests|GrokLogUsageScannerTests|CodexUsageMapperTests|PricingBundledResourceTests|LayoutStoreTests"`
— 179 tests, 1 skipped, 0 failures.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant