High-availability wrapper for Claude Code.
When one Anthropic account hits its usage or billing limit, claude-ha
transparently rotates to the next account and resumes the same conversation
with --resume <session-id> — no manual intervention.
- The wrapper launches Claude Code under the primary account and writes its PID to a per-process file.
- A
StopFailurehook insettings.jsonwatches forrate_limitorbilling_errorevents from the API. When one fires, the hook runsclaude-ha-kill, which signals the wrapped Claude Code process to exit. - The wrapper detects the usage-limit signal in the session JSONL, picks up
the session id, and relaunches Claude Code under the next account with
--resume <session-id>. The conversation continues seamlessly.
A shell function on claude routes interactive use through the wrapper, so
you never have to type claude-ha directly.
Single-account hosts work transparently: if only one config dir from the
account list exists, the wrapper just execs Claude Code with no rotation
overhead.
- Claude Code installed.
- Two Anthropic accounts (e.g. a personal Pro plan and a Team plan), each
logged in to a separate config directory:
~/.claude— primary~/.claude-acct2— secondary
To log in to the secondary account:
CLAUDE_CONFIG_DIR=~/.claude-acct2 claudeFollow the auth flow, then exit. One-time setup.
mkdir -p ~/.local/bin
cp claude-ha claude-ha-kill ~/.local/bin/
chmod +x ~/.local/bin/claude-ha ~/.local/bin/claude-ha-killMake sure ~/.local/bin is on your PATH.
Merge the contents of settings.example.json into your existing
~/.claude/settings.json. The relevant block is:
{
"hooks": {
"StopFailure": [
{
"matcher": "rate_limit",
"hooks": [
{ "type": "command", "command": "claude-ha-kill" }
]
},
{
"matcher": "billing_error",
"hooks": [
{ "type": "command", "command": "claude-ha-kill" }
]
}
]
}
}The hook needs to be active in both account config dirs. The simplest way is to symlink:
mkdir -p ~/.claude-acct2
ln -sf ~/.claude/settings.json ~/.claude-acct2/settings.jsonThe hook is gated on the CLAUDE_HA_PID env var (set only by the wrapper),
so plain claude invocations are unaffected.
Add to your ~/.bashrc (or ~/.zshrc):
# Route `claude` through the HA wrapper.
# The function only exists in your interactive shell; scripts that call
# `claude` directly are unaffected (no recursion).
claude() { command claude-ha "$@"; }
# Optional helpers
alias claudep='claude -p' # print mode
alias claude2='CLAUDE_CONFIG_DIR=$HOME/.claude-acct2 command claude' # force account 2Reload:
source ~/.bashrcOverride defaults with environment variables:
| Variable | Default | Purpose |
|---|---|---|
CLAUDE_HA_ACCOUNTS |
$HOME/.claude:$HOME/.claude-acct2 |
Colon-separated config dirs in priority order |
CLAUDE_BIN |
claude |
Path to the real Claude Code binary |
Example — three accounts, primary first:
export CLAUDE_HA_ACCOUNTS="$HOME/.claude:$HOME/.claude-work:$HOME/.claude-team"Run claude as normal. If the primary account hits its limit mid-session you
will see:
claude-ha: ~/.claude hit usage limit
claude-ha: resuming session <id> on .claude-acct2
The conversation picks up where it left off on the next account.
| File | Purpose |
|---|---|
claude-ha |
Main wrapper script. Iterates configured accounts, launches Claude Code, rotates on usage limit. |
claude-ha-kill |
Hook helper. Reads the wrapper's PID file and signals the wrapped Claude Code process to exit. |
settings.example.json |
The StopFailure hook block to merge into ~/.claude/settings.json. |
An earlier version polled session JSONL files every 2 seconds for usage-limit
markers. The current implementation uses Claude Code's StopFailure hook,
which fires immediately on a structured rate_limit or billing_error event
from the API. Faster signal, no polling overhead, no false positives from
conversational text mentioning "rate limit".
MIT