Skip to content

feat(codex-accounts): switch accounts before a usage limit - #28433

Open
ogarciarevett wants to merge 9 commits into
stablyai:mainfrom
ogarciarevett:feat/codex-auto-switch-before-limit
Open

ogarciarevett wants to merge 9 commits into
stablyai:mainfrom
ogarciarevett:feat/codex-auto-switch-before-limit

Conversation

@ogarciarevett

Copy link
Copy Markdown

ELI5

With this option enabled, Orca chooses a Codex account with more usage left before the selected account runs out. The next Codex session uses that account; sessions already running keep their account. The option is off by default and appears when there are at least two managed Codex accounts.

What Changed

  • Added Settings → Accounts → Codex → Switch accounts before a usage limit, with English strings and Settings search support.
  • Shared the pure policy from feat(claude-accounts): switch accounts before a usage limit #28044 between Claude and Codex: switch at 90% of the tightest current session or weekly window to an account below 90% with at least 10 percentage points more room. At 100%, any account below 100% can be selected without waiting for the cooldown. Stay selected when every account is full.
  • Tagged Codex readings with the managed account ID from the home actually resolved for the fetch. Ignore another account’s reading or a window that has reset, and rank recent readings only from accounts in the same host or WSL distro.
  • Used the existing Codex selection queue and launch routing. Manual account changes start the five-minute cooldown and outrank queued automatic changes. A successful switch produces a notification and one log line without account identifiers.
  • Documented the option on the Codex agent page.

Why

This adds the Codex half missing from #28044, which implements Claude auto-switching. Sharing its provider-neutral policy keeps the thresholds consistent without rewriting the Claude service. Codex retains its own account store and normal selection path, so this needs no credential copying, running-session migration, or new account-selection command.

Linked Issue

Fixes #20512 (the Codex half; the Claude half is #28044, which this PR builds on).

Visual Proof

Settings, AI Provider Accounts, Codex, in an isolated desktop profile with two synthetic managed Codex accounts (Work and Personal, no real credentials).

Before (#28044 head) After (this PR)
Before: Codex accounts without the switch setting After: Codex accounts with Switch accounts before a usage limit

The switch notification is a native macOS notification, outside the renderer capture, so it is not shown; its text is covered by the service tests.

Testing

  • I manually tested these changes locally (isolated desktop run on macOS, synthetic accounts)
  • Automated tests added/updated

Tests were written first and observed failing before implementation. The focused run passes 117 tests in 9 suites, including the existing Claude policy and service suites. Validation on macOS with Node 24.21.0 and Bun 1.4.2:

  • pnpm typecheck: passed, including the final build’s typecheck.
  • pnpm run check:code-quality:changed pr-28044-claude-autoswitch: passed with zero new findings, including design-system checks.
  • Localization catalog, extraction, and coverage checks: passed.
  • pnpm test: 115,840 passed and 50 failed across 21 files. Replaying those files on unchanged feat(claude-accounts): switch accounts before a usage limit #28044 reproduced 36 of the failed cases and the relay-region suite load error. The remaining IPC hidden-spawn failure was traced to an uncached real login-shell probe exceeding its one-second wait; a test-local resolver mock now passes all 9 cases, including with a deliberately slow synthetic shell.
  • pnpm lint: stops on four missing named exports in the untouched main synchronous-spawn ratchet script. Reproduced on unchanged feat(claude-accounts): switch accounts before a usage limit #28044.
  • pnpm build: typecheck, CLI, desktop, web, and mobile-web build and verify successfully. macOS native computer-helper packaging fails because lipo cannot find the expected Swift output paths; reproduced with pnpm run build:native on unchanged feat(claude-accounts): switch accounts before a usage limit #28044.

The fresh worktree also needed pnpm --dir mobile install --frozen-lockfile, because mobile is a separate workspace. No dependency manifests or lockfiles changed.

Re-checked on a second Mac (macOS 26.4.1, arm64, Node 24.13.1) at head ae05e72: pnpm run typecheck passed; the 9 focused suites passed (117 tests, 0 failed); pnpm run lint reports 9 named-import warnings, all reproduced without this change (5 on upstream main, 4 on the #28044 base). The macOS native helper packaging failure above is tracked in #28430 with a separate one-line fix.

AI Disclosure

GPT-6.1-Sol (Codex) helped investigate and write this change and its tests. Gemini 3.8 Flash and Claude Opus 5.5 reviewed it.

Review

  • Cross-platform: reuses host/WSL account selection and shadcn Switch; adds no shortcuts, process spawning, or platform-specific path construction. macOS automated verification only; Windows and Linux were reviewed in source and covered by synthetic runtime cases.
  • SSH and remote: account switching remains on the execution host, and the local setting is hidden when the Accounts pane displays server accounts. No remote process verdict or remote control behavior changes.
  • Agents and integrations: only new Codex launches change account. Claude keeps its existing auto-switch service and calls the shared policy directly. No changes to supervised-worker failover, launch profiles, account CLI commands, or git-provider behavior.
  • Performance: reacts to existing usage updates; inactive account reads are requested only near the threshold and reuse existing caching and debounce behavior. No new poll timer.
  • UI: follows the adjacent Claude setting and existing tokens and Switch primitive. Static markup tests cover visibility and accessible checked state; the screenshots above show the real renderer.
  • Security and compatibility: credentials stay in their provider stores, stale or untagged active readings cannot trigger a switch, and new setting and metadata fields are optional. Switch and failure logs contain no email, token, or home path.
  • The desktop notification shows the from and to account labels exactly as the Claude notification in feat(claude-accounts): switch accounts before a usage limit #28044 does; it is a local notice on the owner’s own screen. Log lines carry no account identifiers.

Agent skill upstream boundary

  • Not applicable: this change copies no upstream skill-installer material.

Notes

This branch is stacked on #28044 at bdbdda4, so the diff against main includes #28044's commits. Please review the commits after bdbdda4; I will rebase onto main when #28044 merges.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred): typecheck passes; full lint, suite and build failures are reproduced on the base, see Testing

adifsgaid and others added 9 commits October 12, 2026 20:46
Opt-in setting. When the selected managed Claude account passes 90% of its
5-hour or weekly window, new Claude sessions move to the managed account on
the same runtime with the most room left. A 10-point margin and a 5-minute
cooldown (also after a manual pick) stop two accounts trading places; at a
hard limit any account with room wins. Accounts that need a sign-in, sit on
another runtime, or have no reading from the last 15 minutes are skipped;
stale readings are refreshed before ranking. Only readings tagged with the
selected account count, and a switch queued behind a sign-in re-decides on
the latest usage and yields to anything the user did meanwhile.
…der way

The usage service skips a read it debounces, one already running, or one where
every account is inside Retry-After. The auto-switch listener recorded the ask
as a read anyway and would not ask again for 15 minutes. It now counts a read
only when the published state shows one in flight.
An idle Claude session keeps reporting the usage of a window that has since
rolled over (the stale statusline snapshots stablyai#23421 describes), and an inactive
account's cached reading can outlive its window. The auto-switch policy counted
both as current, so a stale 96% could trigger a switch and an account whose
window had reset could be skipped as exhausted. A window past its reset time
no longer counts.
A failed read and a live session's statusline post both carry the account they
belong to (usageMetadata.authProvenance), but a successful OAuth read carried
no metadata at all. The auto-switch only trusts a reading tagged with the
selected account, so it never acted on a polled reading: with no session
posting, as when the account has already hit its limit, it stayed put. A
successful read now carries the same credential source and account tag.
…cted account's runtime

Two cases left the auto-switch with nothing to pick. With the default WSL
distro the usage target names no distro while accounts do, so every other
account was filtered out; candidates are now the accounts on the selected
account's own runtime. And an account inside its Retry-After cannot be read,
so waiting for its reading stalled ranking while a usable account was right
there; it no longer waits for those. The docs paragraph now says the switch
needs another account with clearly more room.
…ublish

The auto-switch listener asked the usage service for a read from inside the
service's own publish loop. The service publishes again from within that call,
so the window and paired clients were handed the newer state first and the
older one last. The ask now runs after the publish it reacted to. A read also
counts as tried only when one of the accounts being ranked is fetching, not an
account on another runtime.
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

Claude and Codex gain opt-in automatic account switching when usage reaches the shared threshold. The system attributes usage readings to managed accounts, selects eligible alternatives, and guards queued selections against intervening user actions. Account settings expose the controls, and startup registers switch handlers and optional notifications.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to ae05e

Automatic switching has no confirmed merge-blocking issue from these findings. The remaining comment concerns documentation placement only.

Pre-merge checks: Passed 2 · Failed 3

Failed Failed checks: 3 warnings

Check name Status Explanation Resolution
Linked Issues check Warning The PR implements the Codex opt-in setting, 90%/10-point selection policy, account provenance checks, reset-window handling, runtime filtering, queue routing, and automated tests for #20512. The direc… Complete the remaining #20512 coding requirements, or split and link this partial Codex implementation to a narrower issue: add a configurable threshold; define reset-aware circuit-breaker behavior; support running sessions; provide CLI/hea…
Out of Scope Changes check Warning Most changes support #20512 through shared policy, provider account selection, settings, provenance, documentation, and tests. However, the change to src/main/ipc/pty-runtime-hidden-at-spawn-mark.test… Remove the unrelated IPC test change from this pull request, or move it to a separate pull request.
Docstring Coverage Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 39 functions across 37 files. (3 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 2 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: enabling Codex account switching before a usage limit.
Description check Passed The description covers the required sections and provides clear user impact, implementation details, rationale, linked issue, visual proof, testing results, AI disclosure, review considerations, and c…
Full details: Linked Issues check

Explanation

The PR implements the Codex opt-in setting, 90%/10-point selection policy, account provenance checks, reset-window handling, runtime filtering, queue routing, and automated tests for #20512. The direct issue also requires a configurable threshold, failover for running sessions, reset-aware cooldown until the account resets, CLI/headless parity, and explicit pinning support. The reviewed code keeps running sessions unchanged, uses a fixed 90% threshold and five-minute cooldown, and adds no CLI, headless policy, or pinning behavior.

Resolution

Complete the remaining #20512 coding requirements, or split and link this partial Codex implementation to a narrower issue: add a configurable threshold; define reset-aware circuit-breaker behavior; support running sessions; provide CLI/headless parity; respect explicit account pinning; and add tests for each behavior.

Full details: Out of Scope Changes check

Explanation

Most changes support #20512 through shared policy, provider account selection, settings, provenance, documentation, and tests. However, the change to src/main/ipc/pty-runtime-hidden-at-spawn-mark.test.ts only mocks login-shell environment resolution for unrelated IPC spawn tests. It does not support Codex account switching or the linked issue.

Full details: Docstring Coverage

Explanation

Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 39 functions across 37 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔇 Additional comments: 40
src/main/ipc/pty-runtime-hidden-at-spawn-mark.test.ts: 1

8-11: LGTM!

src/shared/rate-limit-types.ts: 1

70-71: LGTM!

src/main/rate-limits/service/service-types.ts: 1

37-43: LGTM!

src/main/accounts/account-auto-switch-policy.ts: 1

1-62: LGTM!

src/main/claude-accounts/claude-account-auto-switch-policy.test.ts: 1

1-128: LGTM!

src/main/codex-accounts/codex-account-auto-switch-policy.test.ts: 1

1-128: LGTM!

src/main/codex-accounts/codex-account-usage-provenance.ts: 1

6-29: LGTM!

src/main/codex-accounts/codex-account-usage-provenance.test.ts: 1

1-53: LGTM!

src/main/rate-limits/service/service-fetch-targets.ts: 1

50-62: LGTM!

Also applies to: 96-105, 184-187

src/main/rate-limits/service/service-full-cycle-application.ts: 1

24-24: LGTM!

Also applies to: 178-181

src/main/rate-limits/service/service-full-cycle-preparation.ts: 1

34-34: LGTM!

Also applies to: 296-296

src/main/rate-limits/service/service-provider-cycles.ts: 1

77-82: LGTM!

src/main/rate-limits/claude-active-usage-fetch.ts: 1

62-64: LGTM!

src/main/rate-limits/claude-profile-usage.test.ts: 1

103-114: LGTM!

src/main/claude-accounts/claude-account-selection.ts: 1

120-120: LGTM!

src/main/claude-accounts/claude-account-service.test.ts: 1

132-134: LGTM!

Also applies to: 378-401

src/main/rate-limits/service-account-target-selection.test.ts: 1

102-120: LGTM!

src/shared/managed-account-types.ts: 1

100-101: LGTM!

src/main/claude-accounts/service.ts: 1

22-22: LGTM!

Also applies to: 103-119, 156-159

src/main/codex-accounts/service.ts: 1

81-82: LGTM!

Also applies to: 203-203, 248-263

src/main/codex-accounts/service-account-selection-and-removal.test.ts: 1

144-230: LGTM!

src/main/claude-accounts/claude-account-auto-switch.test.ts: 1

1-367: LGTM!

src/main/codex-accounts/codex-account-auto-switch.ts: 1

1-177: LGTM!

src/main/codex-accounts/codex-account-auto-switch.test.ts: 1

1-399: LGTM!

src/main/startup/main-process-account-services.ts: 1

7-11: LGTM!

Also applies to: 87-146, 164-169

src/shared/global-settings-types.ts: 2

323-324: LGTM!


312-313: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

⚠️ Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.

Move the new field below the orphaned Why: comment, or move that comment.

Line 311 is a Why: doc comment about preparing shared ~/.codex. The new codexAutoSwitchEnabled doc comment now sits between that comment and codexManagedAccounts. As a result, TypeScript attaches both comments to codexAutoSwitchEnabled. Move the new lines above line 311.

src/shared/default-global-settings.ts: 1

184-185: LGTM!

src/renderer/src/components/settings/accounts-pane-claude-section.tsx: 1

8-8: LGTM!

Also applies to: 368-399

src/renderer/src/components/settings/accounts-pane-claude-section.test.tsx: 1

157-169: LGTM!

src/renderer/src/components/settings/accounts-pane-codex-section.tsx: 1

7-7: LGTM!

Also applies to: 18-19, 289-319

src/renderer/src/components/settings/accounts-codex-search.ts: 1

1-76: LGTM!

src/renderer/src/components/settings/accounts-pane-codex-auto-switch.test.tsx: 1

1-136: LGTM!

src/renderer/src/components/settings/accounts-search.test.ts: 1

18-21: LGTM!

Also applies to: 45-53, 95-103

src/renderer/src/components/settings/accounts-search.ts: 1

5-8: LGTM!

src/renderer/src/components/settings/AccountsPane.tsx: 1

30-30: LGTM!

src/renderer/src/components/settings/accounts-claude-search.ts: 1

1-46: LGTM!

src/renderer/src/i18n/locales/en.json: 1

19612-19618: LGTM!

Also applies to: 19623-19629

docs/site/content/docs/agents/claude-code.mdx: 1

25-26: LGTM!

docs/site/content/docs/agents/codex.mdx: 1

21-22: LGTM!


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e861a2a9-5637-4dcd-99e2-39955ba3f75a
📥 Commits

Reviewing files that changed from the base of the PR and between 8a0f1ee and ae05e72.

📒 Files selected for processing: 40
  • docs/site/content/docs/agents/claude-code.mdx
  • docs/site/content/docs/agents/codex.mdx
  • src/main/accounts/account-auto-switch-policy.ts
  • src/main/claude-accounts/claude-account-auto-switch-policy.test.ts
  • src/main/claude-accounts/claude-account-auto-switch.test.ts
  • src/main/claude-accounts/claude-account-auto-switch.ts
  • src/main/claude-accounts/claude-account-selection.ts
  • src/main/claude-accounts/claude-account-service.test.ts
  • src/main/claude-accounts/service.ts
  • src/main/codex-accounts/codex-account-auto-switch-policy.test.ts
  • src/main/codex-accounts/codex-account-auto-switch.test.ts
  • src/main/codex-accounts/codex-account-auto-switch.ts
  • src/main/codex-accounts/codex-account-usage-provenance.test.ts
  • src/main/codex-accounts/codex-account-usage-provenance.ts
  • src/main/codex-accounts/service-account-selection-and-removal.test.ts
  • src/main/codex-accounts/service.ts
  • src/main/ipc/pty-runtime-hidden-at-spawn-mark.test.ts
  • src/main/rate-limits/claude-active-usage-fetch.ts
  • src/main/rate-limits/claude-profile-usage.test.ts
  • src/main/rate-limits/service-account-target-selection.test.ts
  • src/main/rate-limits/service/service-fetch-targets.ts
  • src/main/rate-limits/service/service-full-cycle-application.ts
  • src/main/rate-limits/service/service-full-cycle-preparation.ts
  • src/main/rate-limits/service/service-provider-cycles.ts
  • src/main/rate-limits/service/service-types.ts
  • src/main/startup/main-process-account-services.ts
  • src/renderer/src/components/settings/AccountsPane.tsx
  • src/renderer/src/components/settings/accounts-claude-search.ts
  • src/renderer/src/components/settings/accounts-codex-search.ts
  • src/renderer/src/components/settings/accounts-pane-claude-section.test.tsx
  • src/renderer/src/components/settings/accounts-pane-claude-section.tsx
  • src/renderer/src/components/settings/accounts-pane-codex-auto-switch.test.tsx
  • src/renderer/src/components/settings/accounts-pane-codex-section.tsx
  • src/renderer/src/components/settings/accounts-search.test.ts
  • src/renderer/src/components/settings/accounts-search.ts
  • src/renderer/src/i18n/locales/en.json
  • src/shared/default-global-settings.ts
  • src/shared/global-settings-types.ts
  • src/shared/managed-account-types.ts
  • src/shared/rate-limit-types.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

This branch has not been deployed

No deployments
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.

[Feature]: Automatic account failover on rate limit for managed Claude/Codex subscription accounts

2 participants