Rotate multiple Anthropic Claude Code (Max / Pro) subscriptions from a single terminal. claude-rotate probes each logged-in account's live 5-hour and weekly quota, picks the one with the most headroom, writes a full-scope ~/.claude/.credentials.json, and execs the real claude binary — no per-account shadow directories by default, no daily re-login, every Claude Code feature (Remote Control, session-scope commands, …) works.
Session runs on 'personal' (@); next launch rotates to 'work' (>).
╭─────────────┬──────────────────────────────────┬───────────────────────────────────────┬────────╮
│ │ 5h │ week │ sub │
├─────────────┼──────────────────────────────────┼───────────────────────────────────────┼────────┤
│ alt │ ██████████ 100% 14:25 (25m) │ █████████░ 92% Sun 08:00 (18h 0m) │ 4d │
│ Max-5 │ │ →103% Sun 03:02 (13h 2m) │ 17 Jun │
├─────────────┼──────────────────────────────────┼───────────────────────────────────────┼────────┤
│ @ personal │ █████░░░░░ 51% 15:12 (1h 12m) │ ███████░░░ 67% Tue 04:00 (2d 14h) │ 9d │
│ Max-20 │ →67% │ →106% Mon 18:12 (2d 4h) │ 22 Jun │
├─────────────┼──────────────────────────────────┼───────────────────────────────────────┼────────┤
│ > work │ ██░░░░░░░░ 19% 16:42 (2h 42m) │ ███░░░░░░░ 34% Thu 23:00 (5d 9h) │ 22d │
│ Max-20 │ →41% │ →146% Tue 17:42 (3d 3h) │ 05 Jul │
╰─────────────┴──────────────────────────────────┴───────────────────────────────────────┴────────╯
⚠ alt: subscription expires in 4d.
The dashboard sorts accounts by subscription expiry (soonest first) and adapts to your terminal — gradient bars grow with the available width, and below ~76 columns the table folds into one compact card per account. First-column markers: @ the account this session runs on, > the next rotation pick, ★ a pinned account, ⊘ a manually disabled one. Accounts that can't be picked right now (a window at the limit, an expired subscription, or a disabled account) render greyed-out so the eye skips them.
Each account spans two lines per window: a fact line (usage bar, current %, reset clock + countdown) and a dimmed forecast sub-line. The →XX% projects where that quota lands at window reset if the current burn rate holds; when a window will cross 100% before it resets, the sub-line also shows the clock at which that happens (red under an hour away — see alt's weekly). Forecasts drop once a window is already at/over 100% (see alt's 5h, where you're at the wall). A footer flags accounts that need action — a re-login or a soon-expiring subscription. Hide all forecasts with CLAUDE_ROTATE_FORECAST=0.
Built for developers and AI agents that burn through a single Max plan before lunch and want to keep working against Claude Code without hitting the 5-hour wall.
Keywords: claude code rotator · multi-account claude · claude max rotation · anthropic oauth · claude rate-limit workaround · claude subscription manager · claude-code cli
Also searched as: claude account switcher · claude 5-hour limit · claude weekly quota tool · claude max rate limit · claude-code multi-login · claude account manager · claude quota tracker · anthropic multi-account cli · claude pro pooling · rotate claude subscriptions
- Direct your favorite coding agent (Claude Code, Cursor, Aider, Codex, …) to AGENTS.md
- Prompt away!
Requires Python ≥ 3.11 and claude (2.1.117+) on PATH.
1. Install from GitHub (not on PyPI yet):
uv tool install git+https://github.com/evrenverse/claude-rotate
# or: pipx install git+https://github.com/evrenverse/claude-rotate2. Wire up the shell alias — scoped to run so only the rotation happens through the wrapper; claude doctor, claude auth, etc. still hit the real binary untouched:
# ~/.bashrc or ~/.zshrc
alias claude='claude-rotate run'3. Log in each subscription (browser OAuth PKCE, one-time per account):
claude-rotate login work@example.com work
claude-rotate login personal@example.com personal4. Install the background sync (recommended — keeps accounts.json in step with the tokens Claude Code rotates mid-session):
claude-rotate install-sync # adds a */2 * * * * crontab entry5. Verify and go:
claude-rotate doctor # health check
claude-rotate status # live quota dashboard
claude "explain this repo" # picks the freshest account automaticallyEach login opens a browser tab against claude.com/cai/oauth/authorize, runs the PKCE handshake, and captures the callback on a short-lived local port — no token pasting.
| Command | What it does |
|---|---|
claude-rotate run [args…] |
Picks best account, exec claude (default when no command given) |
claude-rotate login <email> [<handle>] |
Add or re-login an account (interactive OAuth PKCE) |
claude-rotate login <email> <handle> --replace |
Overwrite an existing account |
claude-rotate login <email> <handle> --from-env / --token-file <path> |
Headless login (no browser) from CLAUDE_ROTATE_TOKEN / a token file — grants inference-only scope |
claude-rotate list |
Show configured accounts (no network) |
claude-rotate status |
Live dashboard + health exit code |
claude-rotate status --report |
Compact single-table overview: running account (@), next pick (>), per-window resets (clock + weekday + relative) and warnings |
claude-rotate status --json |
Machine-readable state |
claude-rotate pin <name> / unpin |
Force / resume rotation |
claude-rotate disable <name> / enable <name> |
Take an account out of rotation (never auto-picked, still probed + shown greyed-out) / put it back |
claude-rotate set-expiry <name> <value> |
Override subscription expiry (YYYY-MM-DD, Nd, or "") |
claude-rotate rename <old> <new> |
Rename an account handle |
claude-rotate remove <name> |
Delete an account (accepts handle or email) |
claude-rotate sync-credentials |
Reconcile ~/.claude/.credentials.json → accounts.json (cron entry point) |
claude-rotate install-sync / --uninstall |
Install / remove the 2-minute sync crontab entry |
claude-rotate install-hooks / --uninstall |
Install / remove the heartbeat hook in ~/.claude/settings.json for precise active/idle session classification (enables load-aware distribution across concurrent runs) |
claude-rotate install-skill / --uninstall |
Install / remove the bundled agent skill (canonical ~/.agents/skills/account, symlinked into every detected agent) |
claude-rotate cleanup [--yes] |
Delete all rotate state (accounts, cache, logs) |
claude-rotate doctor |
Self-check (binary, config, tokens, refresh-token staleness) |
claude-rotate config get [<key>] / set <key> <value> |
View / toggle features (e.g. session_isolation) |
<name> accepts either the handle (work) or the account's email (work@example.com, case-insensitive).
claude-rotate ships a small agent skill so coding agents can answer "which account am I on / what are my limits" on demand. Install it once:
claude-rotate install-skillThis writes the skill once to the shared store ~/.agents/skills/account and symlinks it into every detected agent — Claude Code (~/.claude/skills), Codex (~/.codex/skills), Gemini (~/.gemini/skills), and opencode (~/.config/opencode/skills). A single edit then updates them all.
Invoke it as /account (or just ask which account is active). The skill is a thin wrapper that runs claude-rotate status --report and shows the result — a single table marking the running account (@) and the next rotation pick (>), with per-window resets and warnings. Update it any time by re-running install-skill; remove it (symlinks + canonical copy) with --uninstall.
On every claude-rotate run:
- Pre-run reconcile — read
~/.claude/.credentials.jsonand sync any in-session token rotation back intoaccounts.jsonbefore picking. - Pick the account with the most 5-hour and weekly quota headroom — honouring a pin (
★) and skipping any disabled (⊘) account. - Refresh the access token if it's older than 4 hours, using the OAuth refresh token stored in
accounts.json. - Write
~/.claude/.credentials.jsonwith full scopes (user:profile,user:inference,user:sessions:claude_code,user:mcp_servers,user:file_upload) — the same shape Claude Code's own/loginproduces. exec claudewithCLAUDE_CODE_OAUTH_TOKENand any inheritedCLAUDE_CONFIG_DIRstripped from the child environment (session isolation re-setsCLAUDE_CONFIG_DIRper account — see below). Claude Code reads the credentials file exactly as it would after a manual/login; every session-scope feature (Remote Control,/code-review ultra, …) works.
The installed sync cron runs every 2 minutes and catches any drift between the two files while a long claude session is running: Anthropic rotates refresh tokens on each in-session refresh, and the cron keeps accounts.json authoritative so the next run never starts with a stale token.
By default every claude-rotate run writes the single global ~/.claude/.credentials.json, so all concurrent claude sessions share one account. Claude Code re-reads that file on every turn — so if you start a second session that rotates to a different account, the already-running session is switched too on its next turn, dropping its server-side prompt cache (expensive re-processing of the whole conversation).
Enable session isolation to keep each session pinned to its own account:
claude-rotate config set session_isolation true # off by defaultWith it on, each run launches claude with a per-account CLAUDE_CONFIG_DIR under ~/.config/claude-rotate/configs/<account>/. That directory symlinks every ~/.claude entry except .credentials.json, which is written real and per-account. Parallel sessions on different accounts then read their own token and can never clobber each other — while history, projects/, /resume, MCP, plugins and any dashboards stay shared (they're symlinks back to the real ~/.claude). Turn it off again with claude-rotate config set session_isolation false, or override per-invocation with the CLAUDE_ROTATE_SESSION_ISOLATION env var (env wins over the config file).
In isolation mode the rotator never touches the global ~/.claude/.credentials.json. Headless consumers — CI scripts, agent workers spawning claude directly — must pin an account explicitly by setting CLAUDE_CONFIG_DIR=~/.config/claude-rotate/configs/<account>; the sync cron keeps every per-account credentials file fresh, so a pinned headless claude always boots with a live token. (Earlier versions mirrored the most recently launched account's token into the global file instead. That silently re-pointed running headless sessions — which re-read the credentials file every turn — at a different account whenever a new interactive session launched, invalidating their org-scoped prompt cache mid-run and re-billing their full context.)
One-time prompt: the first isolated launch for an account opens Claude Code's workspace-trust dialog (it's a fresh config dir). Pick "Yes, I trust this folder" once per account × project.
A single Max-plan session hits the 5-hour quota cap long before the day is over. Claude Code's default /login binds a single ~/.claude/.credentials.json to one account, so round-robining across multiple subscriptions requires swapping credentials by hand — then re-authenticating every ~8 hours when the access token expires.
claude-rotate keeps the same on-disk contract that Claude Code expects (the OAuth-PKCE .credentials.json), but it writes that file per run from a multi-account store and refreshes tokens proactively. Rotation becomes invisible: the claude child sees a normal signed-in session, quotas and Max-plan metadata track correctly, and nothing downstream knows there are multiple accounts underneath.
| Platform | Status |
|---|---|
| Linux (any distro, Python 3.11+) | ✅ |
| macOS | ✅ |
| WSL2 | ✅ |
| Windows native | ❌ |
Two files cooperate:
~/.config/claude-rotate/accounts.json(Linux) /~/Library/Application Support/claude-rotate/accounts.json(macOS) — the multi-account store,chmod 600, parent dirchmod 700. Override the base directory withCLAUDE_ROTATE_DIR=<path>.~/.claude/.credentials.json— the single-account file Claude Code reads at startup. Written fresh by eachclaude-rotate runfrom the chosen account's tokens inaccounts.json(atomic in-place replace,chmod 600). No backup copies are kept:accounts.jsonis the source of truth, so the overwrite is always safe to redo.
The sync cron (claude-rotate install-sync) reconciles these two whenever Claude Code refreshes its own tokens mid-session, so accounts.json never falls out of date.
See SECURITY.md for the threat model and redaction policy.
Anthropic's /oauth/profile endpoint does not surface pending cancellations — a subscription that has been cancelled on claude.ai stays subscription_status: active until the period actually ends. Without the cancel date available via API, the dashboard falls back to the next billing-anchor date, which for an active subscription is the renewal day, not a real end date. If you have scheduled a cancellation and want the real end date, fill it in during claude-rotate login (prompted after the handle) or set it later with claude-rotate set-expiry <name> YYYY-MM-DD.
Anthropic invalidates OAuth refresh tokens after roughly two weeks of "stale idle." claude-rotate doctor warns when an account hasn't been touched for more than 10 days. If it does expire, just re-run claude-rotate login <email> <handle> --replace.
Issues and PRs welcome — see AGENTS.md if you want your coding agent to help and SECURITY.md for the redaction rules before pasting logs.
⭐ If you find this useful, star the repo — it helps other devs with multiple Claude Code subscriptions find it.
MIT — see LICENSE.