Skip to content

Org-aware account identity, removal, and rotation priority (#6, #7) - #28

Merged
MagicalTux merged 1 commit into
masterfrom
feat/org-aware-accounts
Jun 18, 2026
Merged

MagicalTux merged 1 commit into
masterfrom
feat/org-aware-accounts

Conversation

@MagicalTux

Copy link
Copy Markdown
Member

Closes #6, closes #7.

Problem

One Anthropic account (same email / accountUuid) can hold multiple subscriptions across different organizations — e.g. a corporate Pro org and a personal Max org. Identity was keyed only on accountUuid (with name as fallback), so connecting the second org overwrote the first (#6), and --name didn't help because the UUID check won (#7).

Approach

Identity is now accountUuid + organization. This was re-implemented and reviewed line-by-line against master rather than cherry-picked from the closed fork PRs (#15/#17) — per maintainer policy, and because the repo has already been targeted by a malicious soft-fork (#9), external diffs are treated as untrusted.

Changes

  • src/identity.js (new) — orgKey / sameIdentity / matchAccounts / emailOf. sameIdentity treats an unknown org on either side as a match, so a freshly-profiled login backfills a legacy entry instead of duplicating it; once both orgs are known, a different org is a distinct account.
  • fetchProfile extracts organization.uuid; accounts persist orgUuid/orgName.
  • All ~14 dedup/sync/find match sites converted to sameIdentity. syncAccountsFromDisk uses a greedy 1:1 claim so multiple same-person/different-org entries pair correctly instead of all matching the first. The accounts command dedups by (accountUuid, org) and backfills org identity from the profile.
  • Name disambiguation — single org → email; multiple → email (Org), renaming the existing collider too. Names stay unique because they're the user-facing key for remove/api/selection.
  • Org-aware remove + api — resolve by name or email; --org <name|uuid> disambiguates; ambiguous queries list the candidates.
  • Rotation priority — priority field (lower = preferred, default 0) is the primary sort key in _selectNext, with the existing weekly-reset heuristic as the tiebreaker (default 0 everywhere ⇒ unchanged behavior). New teamclaude priority <name> <n> command (--first/--last). Session-reset switching won't demote to a worse priority.

Tests

npm test — 22 passing. New test/identity.test.js (sameIdentity incl. legacy-backfill + two-orgs migration; matchAccounts/email/--org routing) and test/rotation-priority.test.js (priority ordering, tiebreak, default-0 parity). Lint clean (one pre-existing unrelated warning).

Backward compatibility

Existing single-account configs (no orgUuid) run unchanged; an entry is backfilled with its org after one accounts/profile fetch.

Out of scope (future PRs, from the #18 stack)

🤖 Generated with Claude Code

One Anthropic account (email/accountUuid) can hold multiple subscriptions
across different organizations — e.g. a corporate Pro org and a personal Max
org. The old identity model keyed only on accountUuid (with name as fallback),
so adding the second org overwrote the first (#6) and --name couldn't help
because the UUID check won (#7).

Identity is now (accountUuid + organization). Re-implemented and reviewed
against master rather than cherry-picking the closed fork PRs (#15/#17).

- src/identity.js (new): orgKey / sameIdentity / matchAccounts / emailOf, the
  single source of truth for identity and lookups. sameIdentity treats an
  unknown org on either side as a match so a freshly-profiled login backfills a
  legacy entry instead of duplicating it.
- fetchProfile: extract organization.uuid; accounts persist orgUuid/orgName.
- All dedup/sync/find match sites switched to sameIdentity; syncAccountsFromDisk
  uses a greedy 1:1 claim so multiple same-person/different-org entries pair
  correctly. accounts command dedups by (accountUuid, org) and backfills org.
- Name disambiguation: one org -> "email"; multiple -> "email (Org)", renaming
  the existing collider too. Names stay unique (they are the user-facing key).
- Org-aware remove + api: resolve by name or email, --org <name|uuid> to
  disambiguate; lists candidates when ambiguous.
- Per-account rotation priority: `priority` field (lower = preferred, default 0)
  as the primary sort key in _selectNext with the existing weekly heuristic as
  tiebreaker; new `teamclaude priority` command (--first/--last); session-reset
  switching won't demote to a worse priority.
- Tests: identity/matchAccounts/emailOf and priority rotation (22 passing).
- Docs: README, config.example.json, help text.

Out of scope (future PRs from the #18 stack): #14 headless/reload API/prober,
#16 usage-endpoint quota. #13 (429 account-failover) intentionally not taken —
the throttle is IP-based, so the merged #25 back-off is the correct behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MagicalTux
MagicalTux merged commit 28e8c2b into master Jun 18, 2026
1 check passed
MagicalTux added a commit that referenced this pull request Jun 18, 2026
The `priority` field (added in #28) was only consulted when a switch was
forced (current account exhausted/throttled). A healthy lower-priority current
account was never preempted, so a recovered preferred account wouldn't be
resumed until the in-use one ran low.

getActiveAccount now switches to a strictly higher-priority (lower value)
available account even from a healthy current one, while staying sticky within
the same priority tier. The common case — all accounts at the default priority
0 — is unchanged and never thrashes, since no account is strictly higher than
another. Exhaustion-switching and the soonest-weekly-reset heuristic are intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support multiple OAuth subscriptions for the same Anthropic account (UUID) Multiple oauth accounts with same email

1 participant