fix(sync): accept legacy Codex account identity metadata - #1196
Merged
Conversation
This was referenced Sep 5, 2026
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
What this changes
codexidentity in v2 documents, while still requiring its provider history and a valid identity value.Heads-up
Tests
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.git diff --checkpassed.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
codexentry inidentitiesfrom older writers. Those peers were failingUsageHistoryDocument.validate()and being dropped entirely inICloudUsageSyncStore, even when Claude/Codex usage was otherwise valid.validate()now treatscodexidentities as allowed metadata onopenusage.history.v2(still requiring matchingproviders["codex"]and the same identity format rules as Claude). Duplicate identity detection keys onproviderFamily:identityinstead 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.