fix: discover Claude Swap accounts and isolate their usage - #1226
Conversation
|
Thanks for your interest in contributing to OpenUsage! External pull requests must reference an open issue that:
Please discuss the change on an issue first, wait for a maintainer to approve and assign it to you, then reopen this pull request with |
robinebers
left a comment
There was a problem hiding this comment.
This fits our account discovery model, and I've approved issue #1227 and assigned it to @maddada. Approval is for the contribution's scope; the following changes and regression coverage are required before merging.
- Preserve every matching credential source on a merged card. If the same account and organization are available through Claude Code, Desktop, and Swap, they should produce one card that can use any valid matching login. Adding Swap must not remove a working Desktop fallback, and a Desktop-backed card should also be able to use its matching Swap credentials.
- Keep organization names visible. Replacing a card's name with only an email makes personal and work organizations with the same email indistinguishable. Preserve the organization label alongside any account label.
- Document the history limitation: spending without reliable account ownership remains excluded when multiple accounts are known. Solving the broader SDK/Conductor attribution issue is outside this PR's scope.
Required regression test scenario:
- Set up two distinct account-and-organization identities, A and B, both saved in Swap and also discovered through Claude Code and Desktop. Include overlapping sources for the same identity.
- Assert that discovery produces exactly two cards, with stable card IDs across repeated discovery and changes to the default login. Existing layout and pins must remain attached to the same identities.
- Expire or reject the preferred credential and assert that each card falls back to another valid credential for that same account and organization. Cover Desktop-to-Swap and CLI/Swap-to-Desktop fallback. A credential for the other identity must never supply the card's limits.
- Switch the default login from A to B and simulate credential changes while both Swap sessions are running. Assert that limits remain on the correct cards, stale refresh results cannot overwrite a changed login, credential writes stay with the source that supplied them, and vault refresh tokens are never used or written.
- Include shared/copied session history and assert that each account's spending is counted once, never crosses to the other card, and unattributed history remains excluded.
- Add a same-email, different-organization case and assert that it produces separate cards with distinguishable names.
Please add automated regression coverage for these cases and report the results in the PR. Also repeat a live overlap/switching check with Claude Code, Desktop, and concurrent cswap run sessions to confirm that refreshing OpenUsage does not break their logins. These are merge requirements, not optional follow-ups.
…each account card, show organization names, and test credential fallback, account switching, and isolation.
|
Hey @robinebers,
Can work on adding codex-swap next if you want. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cced50edcb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Hey @validatedev! |
robinebers
left a comment
There was a problem hiding this comment.
Please fix these two cases before merging:
-
Keep the default Claude account visible when its login file has no organization ID. When
.claude.jsoncontainsoauthAccount.accountUuidbut noorganizationUuid, adding a valid Swap account bypasses the existing default-provider fallback. The default account is then omitted from the cards. OpenUsage already handles this input; adding Swap accounts should preserve that behavior. We have not established how common this state is in real installations, so this is not a claim about a particular older Claude version. -
Try a working saved login when the default login cannot read usage. A matching default credential without
user:profilecurrently precedes the full-scope Swap session credential. The provider returns a local-only snapshot for the first credential and never tries the working fallback, leaving Session and Weekly unavailable. Prefer live-capable credentials or continue to the next matching candidate when a credential cannot read live usage, while retaining account-and-organization verification.
Please add regression tests showing that both the default and Swap accounts remain available in the first case, and that the matching full-scope session supplies live usage in the second.
Both cases were reproduced with controlled fixtures on cced50edcb1cf754f28648d677500f9ac5edcc0c. The existing focused Claude suite passed with 94 tests passed and 2 skipped; the two additional reproductions failed as described.
Better if you can fix them. Thanks! |
# Conflicts: # Sources/OpenUsage/Providers/Claude/ClaudeLogUsageScanner.swift
…each account card, show organization names, and test credential fallback, account switching, and isolation.
|
This pull request adds 1,060 lines. Pull requests with more than 1,000 added lines are rarely merged because they are difficult to review and often extend beyond the approved issue. Please consider breaking this into a smaller, more surgical contribution. |
…each account card, show organization names, and test credential fallback, account switching, and isolation.
|
@robinebers @validatedev Fixed and checked in real environment and everything is working as requested/expected. |
|
great work @maddada thank you so much merging this now into the next beta. it would be great if you could create a follow-up PR with:
|
Brings in upstream v0.7.12-beta.2 (tip 86df736): Claude usage records whose nested iteration model is null are no longer dropped (robinebers#1261), the Claude plan badge follows Anthropic's live profile (robinebers#1262), an exhausted Devin weekly quota is recognized when its percentage is omitted (robinebers#1251), PostHog 3.72.0, and upstream's Claude Swap account support (robinebers#1226). The fork keeps its own claude-swap cards (config snapshots, read-only live tier with a cached-usage fallback, rate-limit and Keychain cooldowns). Upstream's Swap support keys a slot by the same accountUuid|organizationUuid identity, so wiring both would mint two runtimes for one record id. Its code merges in unchanged but unwired. Conflict resolutions: - ProviderAccountAssembly: keep the fork's claude-swap observations and ClaudeSwapCard minting; do not call ClaudeSwapAccount.discover, and drop upstream's swap card loop and its org-less default-login branch. Keep the fork's accountLabel, adopt upstream's optional organizationID and organizationName. - ProviderAccountsStore: keep both .credentialFile and .claudeSwap. - ClaudeUsageMapper: keep the fork's limits[] windows and add upstream's formatLivePlan. - docs/providers/claude.md: keep the fork's claude-swap accounts section. Test adaptations required by the merge: - Remove the three upstream tests that assert ProviderAccountAssembly mints upstream Swap cards (that path is unwired in the fork). - Add the fork's 8th `detail` wildcard to `.progress` patterns in upstream's new Claude tests. - The claude-swap structural guard skips upstream's ClaudeSwapAccount.swift and forbids the fork's card sources from referencing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WjLwoj52ke83U7NEp3u6MJ
- Last-sync record: upstream v0.7.12-beta.2 (86df736), 2026-09-16. - New conflict-table row: upstream's Claude Swap support (robinebers#1226) shares the fork's claude-swap identity keys; the fork keeps its own cards, leaves upstream's code unwired, removes the three wiring tests, and fences the structural guard. - claude-swap notes: live plan badge port and the widened HTTP double. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WjLwoj52ke83U7NEp3u6MJ

TL;DR
Fixes #1227.
Discover accounts saved by Claude Swap and merge matching Claude Code, Desktop, and Swap logins into one card per account and organization. Cards retain matching credential fallbacks and show the organization before the email.
What was happening
What this changes
user:profilecannot hide a working saved session.main(bb055e2), preserving its ownership and nested workflow fixes alongside Swap history folders.Heads-up
Tests
Latest verification, 2026-09-13:
swift test: 1,345 total, 1,342 passed, 3 skipped, zero failures.swift test --filter 'Claude|ProviderAccountAssemblyTests': 158 total, 156 passed, 2 skipped, zero failures.CONFIG=debug ./script/build_and_run.sh build, then launched the fresh host app. All four discovered Claude cards completed live refreshes successfully.git diff --check: passed.Earlier overlap verification, 2026-09-07:
swift test: 1,328 tests total, 3 skipped, zero failures.cswap runsessions at normal prompts: switched the default work -> personal -> work and forced OpenUsage refreshes from GUI Terminal. Exactly two stable card IDs remained, with work at 0% weekly usage and personal at 52% in both states.git diff --check: passed.Detailed regression and VM report.
Screenshots