Predictive, start-of-session load-balancing across multiple Claude subscriptions — for macOS.
Run several Claude Max/Pro subscriptions and you hit the same wall every week: one account pegged at its 5-hour or weekly limit while another sits idle. cc-pool launches every Claude Code session on the emptiest account, picked from live 5-hour / 7-day usage before the session starts — predictive, not reactive. No proxy in the request path, no manual switching, no waiting for a rate-limit error to learn you picked wrong.
After setup, claude
is an alias for ccp run
and the pooling disappears
into the background. Two hard guarantees underwrite the design: plain claude
on ~/.claude
keeps working untouched — the pool can never log it out — and secrets stay in the macOS Keychain, never in cc-pool's database. How it works has the details.
brew tap yasyf/cc-pool https://github.com/yasyf/cc-pool
brew install yasyf/cc-pool/cc-pool
macOS only. The binary installs as cc-pool
with a ccp
symlink; the default build is pure Go.
Pool two subscriptions and launch Claude on the emptiest one, in about five minutes.
1. Run ccp. On an empty pool it walks you through logging in each subscription — every account gets its own
claude /login
:
$ ccp
✓ Set up cc-pool on this machine.
How do you want to log in?
> Log in now, in this terminal
Name for this account (optional)
> work@example.com
✓ Added work@example.com.
Add another account?
> Yes
✓ Added personal@example.com.
Your pool now has 2 accounts.
Launch Claude on the emptiest account:
ccp run
Wrap `claude` to always launch on the emptiest account?
> Yes
✓ Wrapped `claude` — added an alias to ~/.zshrc.
Restart your shell or run `source ~/.zshrc` to use it now.
Run `command claude` for plain ~/.claude.
2. Check the pool. ccp status --plain
prints the table once; bare
ccp status
(or bare ccp
, now that accounts exist) opens a live TUI with per-account score breakdowns:
$ ccp status --plain
ACCOUNT SCORE 5h used 7d used LIVE RESETS
▸ work@example.com 68.1 22% 46% 0 6:00 PM
personal@example.com 34.8 61% 70% 0 4:30 PM
▸ = next pick · score higher = emptier · 5h/7d = % used
updated 3:58 PM
3. Launch. Run claude
(now wrapped). It announces its pick on stderr,
then execs the real claude
— cc-pool is gone from the process tree before Claude Code draws its first frame:
$ claude
Selected work@example.com · 5h 22% used · 7d 46% used
The Selected
line names the account marked ▸
in step 2 — that match is
your verification. From here on, every claude
lands on whichever account has the most headroom.
The quickstart wraps claude
with alias claude='ccp run'
. command claude
bypasses it — plain claude on ~/.claude
, one keystroke away. Prefer to
leave claude
untouched? Decline the prompt (or pass ccp add --no-alias
) and pick your own name:
alias cl='ccp run'
ccp run
forwards every argument to claude
verbatim — no --
separator
needed. Bare ccp
with flags is shorthand for the same thing:
ccp run --resume
ccp -p "summarize this repo" # auto-converts to `ccp run -p ...`
CCP_ACCOUNT=2 ccp run
forces account 2 instead of auto-selecting. Repeated
launches from the same directory stick to one account for prompt-cache
continuity; those announce Reusing work@example.com (pinned)
instead of
Selected
.
Selection refuses to burn a nearly-reset window: an account with an exhausted
plan window is never picked while any account has headroom, and
ccp select --wait
blocks until one does. If the whole pool is exhausted, the launch falls back to the least-bad account and warns loudly on stderr — that session bills pay-as-you-go credits if extra usage is enabled, or rate-limits until the window resets.
ccp select
prints the chosen config dir on stdout and nothing else. Set the
plugin root too, so the session writes canonical plugin paths into the shared
~/.claude/plugins
(ccp run
and ccp env
both do this for you):
CLAUDE_CODE_PLUGIN_CACHE_DIR="$HOME/.claude/plugins" CLAUDE_CONFIG_DIR=$(ccp select) claude
~/.claude
is never moved and never registered as a pool account. It
stays the canonical config dir, so plain claude
keeps working exactly as
before, and is the shared base every pooled account mirrors. The
pool never touches plain claude's credential or login identity. Every account,
including your main subscription, joins with its own claude /login
, so its token chain is fully independent of plain claude's.
Claude Code namespaces its Keychain credential per config dir: the default
~/.claude
uses the item Claude Code-credentials
; a custom
CLAUDE_CONFIG_DIR
gets a suffixed item Claude Code-credentials-<hash>
.
cc-pool gives each account a real, unique dir (~/.cc-pool/accounts/acct-NN
)
so each gets its own Keychain item, its own independent OAuth grant (its own
refresh-token chain), and runs on its own subscription — never API
billing. Each account dir is seeded with a copy of your ~/.claude.json
with the identity stripped (the account's own login writes its identity), so pooled sessions inherit your settings, MCP servers, and per-project tool approvals instead of running first-run onboarding.
Each account dir presents all of ~/.claude —
projects/
, skills/
,
plans/
, settings.json
, history.jsonl
, the lot — with writes passing
straight back, so every session shares the same workspace and plan-mode plans
persist across pooled sessions. Two providers:symlink(default, zero-dependency): symlinks each top-level entry of~/.claude
into the account dir. New top-level entries are picked up automatically at launch, by the daemon, and byccp doctor --fix
.fuse(optional, live mirror): a passthrough mirror mounted viafuse-t— kext-less, mounted as you, no root — and hosted by a detached cc-poolmount-holder process, so daemon restarts and upgrades never disturb live sessions' mounts. Requires a-tags fuse
build (cgo) and a one-timeNetwork Volumesprivacy grant.
A few entries stay per-account instead of shared: daemon/
and ide/
(Claude's PID-keyed supervisor and IDE lock/socket files, which would collide
across concurrent sessions), backups/
(rotating backups of each account's
.claude.json
), the identity and credential files .claude.json
and
.credentials.json
, .last-update-result.json
(instance-local auto-update
state), and remote-settings.json
(claude's cached per-subscription settings).
Per-account .claude.json
doesn't mean settings fork, though: its shareable
top-level keys — everything except identity, per-project state, and startup
counters — flow from ~/.claude.json
into pooled sessions, so a setting you
change in vanilla claude
reaches every account. One caveat: under the
default symlink overlay the flow is one-way (merged in at launch, base wins),
so changing a shareable setting inside a pooled session reverts at the next
launch — manage shared settings in vanilla claude
, or use the fuse overlay,
whose live merged view writes shareable changes back to ~/.claude.json
(two-way).
The baseline — exact when windows are far from a reset — is:
score = 0.70·(100−util_5h) + 0.25·(100−util_7d)
− 2·active_sessions − 100·rate_limited − 20·stale_or_refresh_failed
Three terms keep the ranking honest near the edges: an imminent reset
earns credit in proportion to how soon the window resets (a 90%-used window
resetting in 10 minutes ranks up, not down); a low-headroom barrier
stops a nearly-exhausted 7-day window from being masked by 5-hour headroom;
and a burn-rate term downranks an account being actively drained.
select
picks argmax. Usage comes from Claude's own /api/oauth/usage
endpoint.
brew services start cc-pool
(Homebrew installs) or ccp service install
(source builds) runs a user LaunchAgent — a root daemon couldn't read your
login Keychain. It polls usage every ~3 min with exponential backoff,
refreshes idle accounts' tokens before they expire (a checked-out session
owns its own refresh; the daemon adopts whatever token it rotated to on
check-in), caches scores, and — with the fuse overlay — supervises the
detached mount holder, which owns the mounts (so daemon restarts and upgrades
never disturb them). ccp add
and ccp init
start it automatically; if it
isn't running, ccp select
auto-spawns it or samples live.
No secrets are ever stored in cc-pool's database — the macOS Keychain is the only secret store.
These two tables cover the full user-facing surface; ccp help <command>
prints the same per command.
| Command | What it does |
|---|---|
ccp |
|
On a terminal — empty pool: guided onboarding; populated pool: status. With flags: shorthand for ccp run |
|
ccp add |
|
Pool a subscription via its own claude /login (auto-inits the pool, starts the daemon) |
|
ccp run [claude args…] |
|
Select the emptiest account and exec claude , forwarding every arg |
|
ccp status |
|
| Per-account usage, score, and sessions — TUI on a terminal, plain table when piped | |
ccp select |
|
| Print the chosen account's config dir on stdout — the composable hot path | |
ccp env |
|
Print shell export lines to launch an account by hand |
|
ccp list |
|
| Static account list: ids, paths, Keychain items | |
ccp doctor |
|
| Check accounts' Keychain items and overlays; repair drift | |
ccp remove <id> |
|
| Remove an account from the pool | |
ccp rename <id> <name> |
|
Rename an account; --auto derives names from account emails |
|
ccp init |
|
Set up the pool and start the daemon (optional — ccp add does this) |
|
| `ccp service install | uninstall |
Manage the daemon and mount holder (delegates to brew services on Homebrew installs) |
|
ccp widget |
|
| Install the Notification Center status widget (Homebrew cask) and show how to enable it |
Flags, by command:
| Command | Flag | Effect |
|---|---|---|
add |
||
--label <name> |
||
| Name for the first account | ||
add |
||
--count <n> |
||
| Add exactly N accounts, no continue prompt | ||
add |
||
--yes , -y |
||
| Add one account and log in right away | ||
add |
||
--run-login |
||
| Log in immediately instead of asking how | ||
add |
||
--no-alias |
||
Don't add a claude shell alias |
||
run |
||
CCP_ACCOUNT=<id> (env) |
||
| Force a specific account instead of auto-selecting | ||
select |
||
--wait |
||
| Wait for an account with headroom instead of failing or using an exhausted one | ||
select |
||
--account <id> |
||
| Force a specific account id | ||
select |
||
--no-daemon |
||
| Don't use the daemon; sample usage live | ||
select |
||
--fresh <dur> |
||
| Reuse cached usage newer than this (live mode) | ||
status |
||
--plain |
||
| Print the plain table instead of the interactive TUI | ||
status |
||
--watch , -w |
||
| Refresh continuously (plain mode) | ||
status |
||
--live |
||
| Force live sampling even if the daemon is running | ||
env |
||
--account <id> |
||
| Account id (defaults to the best account) | ||
doctor |
||
--fix |
||
| Attempt to repair detected drift | ||
remove |
||
--keep-credential |
||
| Keep the account's Keychain item | ||
rename |
||
--auto |
||
| Derive labels from account emails (skips custom labels) | ||
rename |
||
--force |
||
With --auto , overwrite custom labels too |
||
init |
||
--no-service |
||
Don't start the daemon now; ccp add starts it |
||
service uninstall |
||
--force |
||
| Skip the live-session gate (sessions on unmounted dirs will break) | ||
service uninstall |
||
--purge |
||
Also remove all pool accounts and state; never touches ~/.claude |
ccp service uninstall # stop the daemon + mount holder, unmount fuse overlays
ccp service uninstall --purge # ...and remove all pool accounts/dirs/state
brew uninstall cc-pool
~/.claude
and its credential are never touched.
Build with CGO_ENABLED=0 go build ./cmd/cc-pool
; go test ./...
passes with no network, Keychain, or daemon. The manual end-to-end test matrix lives in docs/VERIFICATION.md, release history in CHANGELOG.md, and conventions in AGENTS.md.
PolyForm-Noncommercial-1.0.0 © Yasyf Mohamedali — free for noncommercial use. See LICENSE or the license text online.