Switch Claude Code accounts when one runs out of quota, without losing the conversation.
Shell scripts for macOS. All accounts share one config directory, so an account is only a credential.
ccx # open Claude Code with the account that has the lowest usage
ccuse work # open the account with the label "work"
ccswitch # move the current conversation to another account with quota leftThe demo uses fake accounts and stubbed usage numbers. See docs/demo.md for how it is recorded.
Each Claude Code subscription has a 5 hour limit and a 7 day limit. If you have more than one account, for example a personal one and a work one, there are two common ways to switch between them:
- Log out and log in again with
/login. This is slow, and you lose track of which account has quota left. - Use one
CLAUDE_CONFIG_DIRper account. This works, but then every account has its own history, settings, MCP servers, skills and background daemon. You have to keep them in sync by hand, and you cannot continue a conversation in another account.
This tool keeps one config directory (~/.claude) for all accounts. The only thing
that changes between accounts is the credential, which Claude Code reads from a
different Keychain entry. So history, settings, MCP servers and skills are shared,
and ccswitch can continue the same conversation in another account.
It also adds a status line that shows the usage of the current account, and a warning
with the ccswitch command when the account reaches a threshold (95% by default).
Do not use it if you are not on macOS, if you use an API key instead of a Claude subscription, or if you only have one account.
This is a personal tool with one maintainer. There are no versioned releases. It
depends on two things that are not public, documented interfaces: how Claude Code
names its Keychain entries, and the /api/oauth/usage endpoint. A future Claude Code
version can break it. It is released under the MIT license.
Other limitations:
- It only works on macOS. The credential lookup uses the Keychain through
security, and the scripts use the BSD versions ofstatanddate. - Because all accounts share one config directory, some things are shared that
should not be.
/statusand the start banner show the last account that started, not the account of the current session. This is only cosmetic: billing and rate limits follow the credential, and the status line shows the correct account. The cache of MCP connectors that need authentication is also shared, so if a connector fails to authenticate in one account, the other account can skip it for a few minutes. Deleting~/.claude/mcp-needs-auth-cache.jsonfixes it. ccxandccswitchmake one network request per account before they start, with a 5 second timeout each.- Logging in to a new account is a manual step.
You need:
- macOS. The scripts use the
securitycommand for the Keychain, and the BSD versions ofstatanddate. bash,jq,curlandshasum.- Claude Code installed as
claude, logged in with a Claude subscription. - Optional:
ghto show the pull request of the current branch in the status line,tmuxfor the tmux mode, and bats-core to run the tests.
Then:
-
Clone the repository. The shell functions expect it in
~/projects/claude-multi-account. If you put it somewhere else, setCCM_HOMEto that path before you source the shell file.git clone https://github.com/hernandezsanti/ccuse.git ~/projects/claude-multi-account -
Create your accounts file from the example, and change the labels if you want:
cd ~/projects/claude-multi-account cp accounts.example.json accounts.json
-
Add this line to
~/.zshrc(or~/.bashrc) and open a new terminal:source ~/projects/claude-multi-account/shell/claude-multi.sh
-
Check that it works without starting Claude Code.
CCM_DRY_RUN=1prints the launch command instead of running it:CCM_DRY_RUN=1 ccuse work
The output looks like this:
cc: using account [work] (/Users/you/.claude-work) DRY: CLAUDE_CONFIG_DIR=<unset> CLAUDE_SECURESTORAGE_CONFIG_DIR=/Users/you/.claude-work claude -
Log in to each extra account once. See Add an account.
-
Configure the status line and the
Stophook in~/.claude/settings.json. Use the full path to the scripts:{ "statusLine": { "type": "command", "command": "/Users/you/projects/claude-multi-account/bin/statusline.sh" }, "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "/Users/you/projects/claude-multi-account/bin/stop-notify.sh" } ] } ] } } -
Optional: install the
/fleetslash command. The file uses the path~/projects/claude-multi-account, so edit it if you cloned the repository in a different place.ln -s ~/projects/claude-multi-account/commands/fleet.md ~/.claude/commands/fleet.md
ccx # open Claude Code with the account that has the lowest usage
ccuse work # open a specific account by its label
claude-work # alias for `ccuse work`, created for each account
ccswitch # open another account and continue the same conversationAll these commands pass extra arguments to claude:
ccuse work -n my-session "initial prompt"
ccx --model opusccx checks the usage of every account and picks the one with the lowest 5 hour
usage, among the accounts that are below the threshold on both the 5 hour and the 7
day limit. If all accounts are over the threshold, it picks the one whose 5 hour
window resets first.
ccswitch does the same, but it skips the current account. Without arguments it adds
--continue, and since the history is shared, the conversation continues in the
other account. Use CCM_NO_CONTINUE=1 ccswitch to start a new session instead.
The claude-<label> aliases are created from accounts.json every time the shell
file is sourced. Claude Code starts in a subshell, so no variables stay in your
terminal.
To list the background sessions of all accounts, use bin/cc-fleet.sh or the
/fleet command. It reads jobs/*/state.json and daemon/roster.json from the
directory of each account and does not write anything. A session marked ·dead has no live
worker. If it also says working, it is probably stuck. Use --all to include
finished sessions.
With one shared config directory, the built-in background sessions panel already shows everything. This command is mostly useful for sessions that were started with one config directory per account.
With CCM_TMUX=1, the commands above run Claude Code inside a tmux session. If the
terminal crashes, the session keeps running and you can get it back with
tmux attach -t <name>. The session name is the -n value if you pass one,
otherwise cc-<folder>-<hhmmss>. tmux is not used if you are already inside tmux or
if you pass -p.
t ccuse work -n my-session # same as CCM_TMUX=1 ccuse work -n my-session
ccsessions # list the tmux sessions, to find one after a crashaccounts.json lists your accounts and the warning threshold:
{
"accounts": [
{ "label": "personal", "configDir": "~/.claude" },
{ "label": "work", "configDir": "~/.claude-work" }
],
"threshold": 95
}labelis the name you use withccuseand the name shown in the status line.configDiris the credential directory.~/.claudeis the default account. For the other accounts it is a separate directory that only holds the credential.thresholdis the usage percentage that triggers the warning. The default is 95.- An account can also have
keychainServiceto force the name of the Keychain entry, if the derived name does not match.
Use bin/cc-new-account.sh. It creates the directory, adds the account to
accounts.json (and keeps a backup of the old file), and checks the Keychain. It is
safe to run more than once.
bin/cc-new-account.sh work # credential dir: ~/.claude-work
bin/cc-new-account.sh work ~/other/dir # explicit credential dir
bin/cc-new-account.sh --check work # only checks, does not write anythingIf there is no credential yet, the script prints the login command. You have to run it yourself, in a real terminal and not inside Claude Code:
CLAUDE_SECURESTORAGE_CONFIG_DIR="$HOME/.claude-work" claude # then /login and /exitDo not use CLAUDE_CONFIG_DIR to log in. If you log in with the wrong variable, you
can overwrite the credential of another account without any warning.
If you used one CLAUDE_CONFIG_DIR per account before, you do not need to log in
again. The Keychain entry is derived from the same string, so the same directory path
points to the same credential. Do not rename those directories, or the credential
will stop resolving.
The status line shows everything in one row:
work · feature/login · 3 uncommitted · 1a2b3c4d · Opus 5 · xhigh · $1.42 · ctx 19% · 5h 12% ↻23:50 · 7d 44% ↻06/07 05:00
From left to right:
- The account label. It comes from the credential of the session, not from the config directory.
- A git segment, only inside a repository: the branch,
worktreeif it is a linked worktree, the number of uncommitted files, the number of unpushed commits, andno upstreamif the branch has no upstream. Onmainormasterthe segment is shown in red with the wordPRODUCTION. - The pull request of the branch and its CI status, if
ghis installed and the branch is pushed (not onmainormaster). The data comes fromgh pr view, which runs in the background and is cached for 2 minutes instate/, so the status line never waits for the network. - The first 8 characters of the session id, linked to the transcript file.
- The model, the effort level and the session cost.
- The context window usage.
- The 5 hour and 7 day usage, each with the time it resets.
Percentages are green below 75%, yellow from 75%, and red from the threshold. When
the 5 hour or the 7 day usage reaches the threshold, the line adds a red warning:
⚠️ 95%: switch account with ccswitch.
The status line also saves the last usage of each account in state/. The Stop
hook (bin/stop-notify.sh) reads that file when a turn ends. If the current account
is over the threshold, it shows a message that suggests ccswitch. It does not call
the API. It ignores readings older than 10 minutes (CCM_NOTIFY_FRESH) and does not
repeat the warning within 5 minutes (CCM_NOTIFY_COOLDOWN).
Claude Code reads its credential from the macOS Keychain. The name of the Keychain
entry is derived from the exact string in CLAUDE_SECURESTORAGE_CONFIG_DIR: it is
Claude Code-credentials followed by - and the first 8 characters of the SHA-256 of
that string. When the variable is empty, there is no suffix. When the variable is not
set, Claude Code uses CLAUDE_CONFIG_DIR instead.
So each extra account has an almost empty directory, for example ~/.claude-work.
Its only job is to be a stable string that points to one Keychain entry. When you run
ccuse work, the tool starts Claude Code like this, inside a subshell:
unset CLAUDE_CONFIG_DIR
CLAUDE_SECURESTORAGE_CONFIG_DIR=~/.claude-work claudeThe default account (~/.claude) is started with the variable set to an empty
string, not unset. An empty value selects the default credential even if
CLAUDE_CONFIG_DIR is exported in your shell.
Usage numbers come from two places:
- While a session runs, Claude Code passes
rate_limits.*.used_percentageto the status line command. These values come from the API responses, so they belong to the credential of that session. - Before a session starts,
bin/cc-usage.shcallsGET https://api.anthropic.com/api/oauth/usagewith the token of each account, which it reads from the Keychain. This is howccxandccswitchchoose an account.
| Variable | Default | Purpose |
|---|---|---|
CCM_HOME |
~/projects/claude-multi-account |
Where the repository is. |
CCM_ACCOUNTS_FILE |
$CCM_HOME/accounts.json |
The accounts file. |
CCM_BASE_DIR |
~/.claude |
The default config directory. cc-new-account.sh refuses to add it as a new account. |
CCM_STATE_DIR |
<repo>/state |
Where the usage and pull request caches are saved. |
CCM_TMUX |
not set | 1 runs Claude Code inside tmux. |
CCM_NO_CONTINUE |
not set | 1 makes ccswitch open a new session. |
CCM_NOTIFY_COOLDOWN |
300 |
Seconds between two warnings from the Stop hook. |
CCM_NOTIFY_FRESH |
600 |
Maximum age, in seconds, of a usage reading for the hook. |
CCM_DRY_RUN |
not set | Print the launch command instead of running it. |
CCM_USAGE_CMD |
bin/cc-usage.sh |
The command that returns the usage of an account. The tests and the demo replace it. |
CCM_PICK_CMD |
bin/cc-pick.sh |
The command that picks an account for ccx and ccswitch. |
CCM_DUMP_INPUT |
not set | A file where the status line writes the JSON it receives, for debugging. |
The tests use bats-core. They use stubs and temporary directories, so they do not touch your real accounts or Keychain entries.
bats test/MIT. See LICENSE.
