subswapper is a small Go CLI that manages isolated Claude Code
and Codex account homes on one machine. Claude
launches use a separate long-lived setup token for each account. Subswapper
tracks trusted usage and chooses a home without changing credentials in an
existing process.
- Permanent account homes — creates private per-account directories for
CLAUDE_CONFIG_DIRandCODEX_HOME, with commands to log in, print the environment, and launch a client in the selected home. - Live usage tracking — uses fresh provider usage data. Claude setup-token accounts fall back to Claude's normal status-line response data when the OAuth usage endpoint rejects inference-only tokens.
- Quota-aware routing — a monitor loop changes the preferred account when the selected one crosses a configurable threshold, without mutating a running provider's credentials.
- Live swapping through a local proxy — an optional loopback auth proxy holds the setup tokens, picks the account per request, fails over on a rejected rate limit, and records the real rate-limit headers. Running Claude sessions keep going across a switch.
- Safe by design — cross-process locking, private
0700homes, atomic credential persistence, and non-destructive migration from old snapshots. - Extensible — any other service can be managed by listing its credential
files in the config; plug in a custom
usage_commandfor usage probing.
Requires Go 1.26.6 or newer.
go install github.com/lawzava/subswapper/cmd/subswapper@latestOr build from source:
git clone https://github.com/lawzava/subswapper.git
cd subswapper
go build ./cmd/subswapperFor Codex usage probing, the codex CLI must be on PATH (see
Usage probes).
# Create the default config (Claude Code + Codex)
subswapper init
# Create permanent homes
subswapper home create -service claude -account personal
subswapper home create -service codex -account personal
subswapper home login -service codex -account personal
# Store each pre-created Claude setup token through a hidden prompt
subswapper home token set -service claude -account personal
subswapper home create -service claude -account work
subswapper home token set -service claude -account work
# See every account's usage windows
subswapper status
# Select a preferred account, or let subswapper pick the least-used one
subswapper switch -service claude -account work
subswapper switch -service all -account auto
# Run a client with the selected account home
subswapper home run -service claude -- claude
# Add missing shared user-configuration links to an existing Claude home
subswapper home repair -service claude -account work
# Keep the preferred route current in the background
subswapper monitorstatus prints one row per registered account:
subswapper status 2026-07-02T14:07:31Z
SERVICE ACCOUNT SELECTED 5H WEEKLY FABLE5 SCORE STATE
------- ------- -------- -- ------ ------ ----- -----
claude personal yes 62% reset Jul02 15:00 31% reset Jul05 23:00 18% reset Jul05 23:00 62% ready
claude work 12% reset Jul02 19:00 8% reset Jul07 11:00 4% reset Jul07 11:00 12% ready
codex personal yes 91% reset Jul02 16:30 44% reset Jul06 09:00 - 91% ready
FABLE5 is the weekly window scoped to Claude's Fable models (- for
accounts without one). SCORE is the worst of an account's windows — the
value auto-switching compares.
| Command | Description |
|---|---|
init |
Write a starter config file. |
home create -service <name> -account <name> [-email <label>] |
Create and register a private account home. New Claude homes inherit allowlisted user configuration. |
home repair -service claude [-account <name>] |
Add missing allowlisted user-configuration links without replacing existing entries. |
home token set|status|remove -service claude [-account <name>] |
Manage a Claude setup token without printing its value. set accepts the token only through stdin or a hidden prompt. |
home login -service <name> [-account <name>] |
Run the provider's legacy login command in that account home. Do not use this for setup-token routing. |
home path|env -service <name> [-account <name>] |
Print a home path or shell export for configuring other tools. |
home run -service <name> [-account <name>] [-- command...] |
Run a command with the selected account's home environment. |
home migrate |
Copy legacy snapshots into native home filenames without deleting or overwriting files. |
capture -service <name> -account <name> [-email <label>] |
Import the current login into a home; retained for migration and bundle-mode services. |
switch -service <name> [-account <name>|auto] |
Change the preferred route; auto picks the least-used healthy account. |
switch -service all -account auto |
Auto-pick the best account for every service at once. |
status (alias list) |
Show every captured account with usage windows, score, and state. |
monitor [-interval 5m] [-once] [-no-auto] [-verbose] [-proxy] |
Poll usage on a loop and auto-switch when thresholds are hit. Continuous mode logs events; -verbose prints every table; -proxy also serves the Claude auth proxy. |
proxy [-service claude] [-listen 127.0.0.1:7878] |
Serve only the Claude auth proxy configured by proxy_listen. |
remove -service <name> -account <name> [-force] [-delete-home] (alias rm) |
Unregister an account; preserve its home unless deletion is explicit. Remove a Claude setup token first. |
import-cswap [-root <dir>] |
Import accounts from an existing claude-swap (cswap) install. |
version |
Print the subswapper version. |
All commands accept -config <path> (default
~/.config/subswapper/config.json on Linux).
Configure any process supervisor or agent launcher to start Claude through Subswapper:
subswapper home run -service claude -- claudeSubswapper selects the routed account for each new process. It injects the
account's setup token, a controlled CLAUDE_CONFIG_DIR, and
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1. It removes conflicting Anthropic,
Bedrock, Vertex, and Foundry variables described by Anthropic's
environment guide. Changing the
route does not change an existing agent.
For Codex launchers that support shadow homes, use a shared CODEX_HOME
(normally ~/.codex) and each Subswapper Codex account path as its shadow
home. This keeps every auth.json private while the launcher shares sessions.
Without the proxy below, a process keeps the token it was launched with.
Start a new process through home run to use a newly selected account.
Set proxy_listen on the Claude service and run the proxy next to the
monitor:
{
"name": "claude",
"kind": "claude",
"account_mode": "home",
"proxy_listen": "127.0.0.1:7878",
"shared_runtime_home": "native"
}subswapper monitor -interval 5m -proxy # or: subswapper proxy
subswapper home run -service claude -- claudeWhile the proxy is reachable, home run launches Claude with
ANTHROPIC_BASE_URL pointing at the proxy and a per-install placeholder
secret in CLAUDE_CODE_OAUTH_TOKEN. Real setup tokens never enter the
process environment. For every request the proxy replaces the bearer token
with the selected account's token, so switch and the monitor take effect on
the next request of every running session. If the proxy is down, home run
warns and falls back to a fixed-token launch.
The proxy reads Anthropic's anthropic-ratelimit-unified-* response headers
and stores them as proxy_usage for the account that served the request.
This is the usage source for setup-token accounts, which the OAuth usage
endpoint rejects. An unused account keeps its last sample; a window counts as
0% once its reset time passes, and the proxy corrects the estimate on the
first real response. When a response is rejected for a rate limit (HTTP 429
or anthropic-ratelimit-unified-status: rejected), the proxy replays the
buffered request against the least-used alternative and makes that account
the selected route. A 401 marks the token rejected for 30 minutes.
shared_runtime_home: "native" is only valid with proxy_listen. Proxy
launches then leave CLAUDE_CONFIG_DIR alone, so Claude uses its own
~/.claude: settings, hooks, plugins, skills, MCP servers and their OAuth
state, transcripts, --resume, and auto-memory are the same for every
account and for plain claude. The account homes are still used when the
proxy is down, because a fixed-token launch must not touch the native home.
Any other absolute shared_runtime_home keeps a separate managed directory.
The listen address must be a loopback address; the port is fixed so launched processes can find it. Requests without the placeholder secret get 401. Request bodies are buffered up to 64 MiB for replay. Each switch costs one prompt-cache miss.
Claude treats CLAUDE_CONFIG_DIR as the location for every documented
~/.claude path. Subswapper keeps runtime state in each account home and links
only this explicit user-configuration allowlist from the native ~/.claude:
Shared from native ~/.claude |
Kept inside each account home |
|---|---|
CLAUDE.md, settings.json, keybindings.json |
.credentials.json, .config.json, mcp-needs-auth-cache.json |
plugins/, skills/, agents/, output-styles/ |
projects/, including transcripts and auto-memory |
rules/, commands/, workflows/, themes/ |
sessions/, history.jsonl, agent-memory/, and all other generated state |
This allowlist follows Claude's documented
user directory layout.
Missing native sources are skipped and reported. Existing files, directories,
and links to other targets are conflicts and are never replaced. Run
subswapper home repair -service claude -account <name> after an upgrade to
repair an existing home. The command is idempotent and reports names only; it
does not read or print file contents.
Subswapper uses symbolic links so later user-configuration changes apply to every account. On Windows, enable Developer Mode or grant symbolic-link privilege. Creation or repair stops with an explicit error if Windows cannot create a link.
By default, each Claude account has a separate runtime home. User-scoped MCP configuration and MCP OAuth state therefore follow the selected account home. To keep one MCP state across setup-token account switches, configure a shared runtime home:
{
"name": "claude",
"kind": "claude",
"account_mode": "home",
"shared_runtime_home": "~/.local/share/subswapper/shared/claude"
}Every setup-token launch then uses that path as CLAUDE_CONFIG_DIR.
Subswapper still selects and injects one account token per new process. Usage
captured through Claude's status line remains bound to that token and account.
Existing processes keep their launch token.
The shared directory keeps MCP configuration, MCP OAuth, settings, plugins,
credentials, history, sessions, and auto-memory together. This option disables
the account-state isolation described above. Subswapper removes only stale
top-level oauthAccount metadata before launch. It preserves .credentials.json
contents and secures the file to 0600. The native user home and native
~/.claude directory remain prohibited as shared runtime paths.
An empty shared directory starts without user-scoped MCP registrations. Seed
it from one trusted Subswapper account home while no Claude process uses that
home, or configure MCP servers again in the shared runtime. The
shared_runtime_home path must resolve to an absolute path. Omitting it keeps
the original per-account behavior.
monitor evaluates every service each cycle. With automatic switching
enabled, a service moves to the captured account with the lowest worst-window
usage only when all of these hold:
- the active account has reached the switch threshold (default 90%) in its 5-hour, weekly, or Fable weekly window;
- the best alternative improves the worst-window score by at least the minimum improvement (default 10 percentage points);
- the cooldown since the service last switched accounts — manually or automatically — has passed (default 30 minutes).
Proxy samples are trusted without an age limit, since an unused account's
windows only fall until their reset. Both pacing rules are skipped when the
active account is exhausted or its stored credentials stop working. The monitor escapes to the best healthy
account on the next cycle. Claude accounts with missing, expired, rejected, or
unsafe setup tokens are never selected. Accounts without fresh trusted usage
are also excluded. A manual
switch -account auto always forces the best account immediately.
In account-home mode, switching updates routing state only. Existing launcher
or CLI processes are not silently rebound; start the next command through
home run. Explicit custom file-bundle services retain the legacy
transactional switching behavior.
subswapper init writes a config like this:
{
"monitor": {
"interval": "5m",
"auto_switch": true
},
"services": [
{ "name": "claude", "kind": "claude", "display_name": "Claude Code", "account_mode": "home" },
{ "name": "codex", "kind": "codex", "display_name": "Codex", "account_mode": "home" }
]
}The monitor block accepts these knobs (defaults shown):
"monitor": {
"interval": "5m",
"auto_switch": true,
"switch_threshold": 0.90,
"min_improvement": 0.10,
"cooldown": "30m"
}Top-level backup_root and state_path override where account homes and state
are stored. (backup_root keeps its historical name for compatibility.)
Built-in services without explicit files default to account_mode: "home".
Account-home files use these native names:
- Claude:
<account-home>/.credentials.jsonand optional.config.json - Codex:
<account-home>/auth.json
Claude home-mode services may set shared_runtime_home. This changes only the
runtime home used by setup-token launches and status-line settings. Registered
account homes and setup-token storage remain separate. proxy_listen enables
the local auth proxy; proxy_upstream overrides the API origin for testing.
An explicit files list defaults a service to account_mode: "bundle". This
keeps custom-service support and legacy transactional switching available.
Codex can also store credentials in an OS keyring. subswapper manages
file-backed credentials only, so configure Codex with:
cli_auth_credentials_store = "file"Claude setup tokens are not stored in account homes. Token files use 0600
mode under separate 0700 directories. Token values never enter config or
state. Identity remains unknown when Anthropic does not expose a trusted
accountUuid for an inference-only token. Anthropic documents setup-token
creation and lifetime in its authentication guide.
The isolation design was also compared with the MIT-licensed
claude-code-account-switcher;
Subswapper uses an independent implementation.
Claude home-mode usage first probes Subswapper's existing OAuth usage endpoint with the setup token. Subswapper never refreshes, exchanges, or rotates a setup token. If the endpoint does not support that token, a launch wrapper captures the documented five-hour and seven-day limits from Claude's normal status-line response data. It runs any existing user or workspace status-line command with the original input. Cached data expires after five minutes and is bound to the random revision of the current token. Anthropic documents the response fields in its status-line guide.
Anthropic does not document a Fable-specific status-line window. FABLE5
therefore shows - for status-line-only setup-token accounts. Subswapper
reports usage as unavailable when neither a direct response nor a fresh,
complete status-line sample exists.
Codex usage is read through the local codex app-server JSON-RPC
interface using the permanent account CODEX_HOME, so an official credential
refresh stays in that home. Subswapper does not create a disposable copy of
the refresh token. Current plans may expose only a weekly
window; any available provider window is displayed and included in scoring.
This requires ChatGPT auth in file storage; API-key mode has no subscription
limits to read.
Custom services (or overrides) can set usage_command. The command runs
once per captured account with these environment variables:
SUBSWAPPER_SERVICESUBSWAPPER_ACCOUNTSUBSWAPPER_EMAILSUBSWAPPER_ACCOUNT_DIRSUBSWAPPER_BACKUP_ROOT
It must print JSON with both five_hour and weekly windows (output missing
either is rejected):
{
"five_hour": { "pct": 25, "resets_at": "2026-07-01T23:00:00Z" },
"weekly": { "pct": 16, "resets_at": "2026-07-05T23:00:00Z" }
}Windows may alternatively be given as { "used": 25, "limit": 100 }. An
optional third window, fable_weekly, is also accepted and counts toward
exhaustion and autoswitch scoring.
To replace an existing cswap install, import its stored Claude accounts
directly:
subswapper import-cswapThis reads the claude-swap data directory (default
~/.local/share/claude-swap on Linux), decodes its stored credential backups,
copies config snapshots, and imports the usage cache, naming each slot
cswap-N. cswap's active slot is adopted as the active account when the live
credential files still match it. After import, the cswap binary is no
longer needed.
After upgrading an existing installation, run:
subswapper home migrate
subswapper home repair -service claude -account <name>Legacy Claude credentials.json and claude.json snapshots are copied to
their native home names, .credentials.json and .config.json. Existing
native files win; nothing is overwritten or deleted. Codex auth.json files
already have their native filename. home repair adds only missing allowlisted
Claude user-configuration links. Verify with subswapper status, then use
home path to configure external launcher instances.
Do not restart a launcher or Subswapper for this migration. Existing agents keep their original process environment.
- List active agents and record the current Subswapper route.
- Obtain explicit approval before creating any setup token.
- Open a private browser profile that is signed out of Claude.
- Run
claude setup-tokenand authenticate only the intended subscription. - Abort if the browser identity is absent or ambiguous.
- Run
subswapper home token set -service claude -account <name>. - Paste the token only into the hidden prompt.
- Run
subswapper home token status -service claude -account <name>. - Launch one explicit test process with
subswapper home run -service claude -account <name> -- claude. - Wait for a normal response and fresh usage before enabling auto-selection.
- Repeat for each account.
- Obtain explicit approval before replacing or removing any live token.
Rollback affects only new launches. With approval, use home token remove for
the affected account. Existing processes retain the token supplied at launch.
Defaults on Linux (macOS and Windows use their native config/data folders):
- config:
~/.config/subswapper/config.json - state:
~/.local/share/subswapper/state.json - account homes:
~/.local/share/subswapper/accounts/ - Claude setup tokens:
~/.local/share/subswapper/tokens/ - Claude proxy secret:
~/.local/share/subswapper/tokens/claude/proxy-secret.json
Linux and macOS are tested in CI; Windows builds are cross-compiled but
currently untested. Claude setup-token storage requires POSIX 0600 and
0700 permission checks, so it fails closed on Windows. Other Windows support
remains experimental. Claude user-configuration inheritance also needs Windows
Developer Mode or symbolic-link privilege.
Credentials and state are written with 0600 permissions under 0700
directories. Setup-token replacement uses atomic rename and directory sync.
Home removal preserves the directory by default;
-delete-home is required to erase it. Explicit bundle-mode changes still use
rollback snapshots and a recovery journal. Treat the account root like a
password store — it holds working OAuth tokens.
To keep the monitor running, a systemd user unit works well:
# ~/.config/systemd/user/subswapper.service
[Unit]
Description=subswapper account-home quota monitor
[Service]
ExecStart=%h/go/bin/subswapper monitor
Restart=on-failure
[Install]
WantedBy=default.targetsystemctl --user enable --now subswapperThe default five-minute monitor writes startup, switch, failure-transition,
and recovery events. Use monitor -verbose only for interactive diagnostics
when a complete status table every cycle is useful.
Contributions are welcome — see CONTRIBUTING.md for the development workflow. Please report security issues privately (see SECURITY.md).