Catalog PyPI
PACKAGE EXPLORERView in registry

acctswap

PyPI

Switch the account every Claude Code session on a machine uses, from saved logins, without a browser

accountclaude-codecliswitch
Versions collected1
Direct dependencies0From release 0.2.3
Last observed20 Oct, 18:02 UTC

README

Version 0.2.3 documentation

acctswap

Switch the Claude account that every Claude Code session on a machine uses, without a browser login.

Not affiliated with or endorsed by Anthropic. Claude and Claude Code are trademarks of Anthropic, PBC.

Claude Code keeps one shared saved login per machine, and every open session re-reads it. That is why one /login moves every open tab. acctswap does the same swap from saved copies of each account's login, so switching takes a second, needs no browser, and keeps background work running.

The examples use the short name sw (see Install).

sw                  switch to the other saved account (when there are two)
sw <name>           switch to the account with that short name, or part of its email
sw active           when the active account reads full, switch to the one with the most room
sw status           every saved account with its usage; saves a login sw has not seen
sw save             save the active login (after a /login of an account sw has not seen)
sw name <name>      give the active account a short name, e.g. `sw name me`
sw hook install     switch automatically when Claude refuses a request on a usage limit
sw hook uninstall   remove that hook

Inside a session, run it as ! sw; it works even when the account is at its limit, because it does not use the model. Open sessions follow on their next message. Remote Control drops on any account change (Claude Code's own behaviour, also after /login), so run /remote-control in tabs that message other machines.

Install

WithCommand
Homebrew (macOS, Linux)brew install cj-ways/acctswap/acctswap
npmnpm install -g @cj-ways/acctswap (carries the program for all six systems, 3 MB; the other ways install only yours, 0.5 MB)
pip or uvpip install acctswap, or uv tool install acctswap
cargocargo binstall acctswap (downloads the release) or cargo install acctswap (builds it)
By handdownload the archive for your system from the releases, check it against SHA256SUMS, and put acctswap on your PATH

Releases are built on GitHub Actions; gh attestation verify <file> -R cj-ways/acctswap shows which workflow and commit built a file. Releases up to 0.2.2 were built before the repo moved to cj-ways: check those with --owner 000Janela000 instead of -R.

The short name sw: add a small script named sw next to your other commands, so it works in every shell and inside Claude Code (! sw), wherever the package manager put acctswap:

  • macOS, Linux: printf '#!/bin/sh\nexec acctswap "$@"\n' > ~/.local/bin/sw && chmod +x ~/.local/bin/sw
  • Windows: a copy of acctswap.exe named sw.exe, in the same folder. (Claude Code runs commands through Git Bash, which does not run a sw.cmd by the bare name sw.) Copy it again after each update.

Then, once per account: /login to it in Claude Code and run sw save; give each a short name with sw name <name>. To switch automatically on a usage limit: sw hook install.

Coming from claude-sw (the old name): the first run moves ~/.config/claude-sw to ~/.config/acctswap, and each login saved in the macOS Keychain moves to the new name the next time it is used. Run sw hook install once to replace the old hook.

From source: cargo install --locked --path crates/acctswap.

How it works

macOSLinux, Windows
Active loginKeychain item Claude Code-credentials; with a custom CLAUDE_CONFIG_DIR, Claude Code-credentials-<first 8 hex digits of the folder's SHA-256>~/.claude/.credentials.json (or in CLAUDE_CONFIG_DIR)
Saved loginsKeychain items acctswap (account = email)~/.config/acctswap/saved/<email>.json, owner-only (the folder too, on Linux)
Account record~/.claude.json → oauthAccountsame

A switch replaces only the Claude account part of the active login (claudeAiOauth); MCP server logins stored next to it stay. It also sets oauthAccount in ~/.claude.json and drops that account's cached data.

Usage comes from https://api.anthropic.com/api/oauth/usage, the endpoint Claude Code's own usage meter calls, through the system's curl (built into macOS, Linux distributions and Windows 10+). The token goes to curl in a config on stdin, never on the command line. The active account is asked live; others reuse a reading younger than two minutes, because the endpoint rate-limits frequent callers.

The switch protocol

  1. Read the signed-in email and tokens; ask https://api.anthropic.com/api/oauth/profile whose tokens they are. A mismatch means a /login is half-way through: nothing is saved, and sw retries a moment later.
  2. Do every network call without holding a lock.
  3. Take Claude Code's token-renewal lock (~/.claude/.oauth_refresh.lock), re-read, and commit only if nothing changed since step 1: save the tokens being left, write the target's tokens, then its account record. If writing the account record fails, the previous login is put back.

The whole run also holds sw's own lock, so tabs that hit a limit together switch once.

When an account counts as used up

The usage endpoint rounds to whole percents, and an account that reads 100% can still answer for a while. Two accounts can both read "weekly 100%, critical" while only one of them is refused. So a reading only ranks accounts; it never makes sw call an account used up.

  1. The hook (sw hook install) runs sw active --from-hook when Claude refuses a request on a rate limit. That refusal is the signal: sw skips the active account until the first of its windows that reads full resets (30 minutes when none reads full, for example a limit on one model), and switches to the best account it has not seen refused, even one that reads 100%.
  2. sw never switches to an account it has seen refused, so it cannot bounce between two accounts that are both turned away. When every account was refused, it stays and says which one frees up first.
  3. A refusal reported within two minutes of a switch belongs to the account just left, when that account reads full or its usage is unknown: a request sent before the switch came back refused after it. sw marks that account and stays. When the account just left still has room, the refusal is the active account's, as in rule 1.
  4. sw active by hand, without a refusal, moves off an account that reads full only to an account with room; sw <name> always switches.

What is verified

CheckWhat it coversWhere
Model checking (TLA+ / TLC)The protocol against a second sw run, token renewals and /login, in every interleaving; and that removing any one protection breaks itspec/, spec/check.sh
Proofs (Kani)For every input within bounds: the account choice never leaves an account with room that was not refused; never picks a refused account and always picks the best-ranked one; leaves a refused account whenever another was not refused; says "every account was refused" only when true; a refusal always ends in the future, at a full window's reset or after the fallback. Timestamp parsing never panics; dates and timestamps round-trip exactly#[cfg(kani)] in crates/acctswap-core
Tests100% of lines, functions and branches of the core, including every port failing on every callcargo llvm-cov -p acctswap-core
Mutation testingBugs planted in the core are caught by the testscargo mutants -p acctswap-core
LintsClippy pedantic, no unsafe, no unwrap outside testsCargo.toml [workspace.lints]
DependenciesOne JSON library and its three helpers; licences, sources and advisories checkeddeny.toml

What acctswap does with your login

  • It reads and writes Claude Code's saved login on this machine, and keeps a copy of each account's login: in the macOS Keychain, or in owner-only files elsewhere.
  • The tokens go to one place only: api.anthropic.com, to read each account's usage and to ask whose tokens they are. They go through the system's curl, in a config on stdin, never on a command line.
  • Nothing else is sent anywhere. There is no telemetry and no server of ours.

Limits

  • It relies on how Claude Code stores the login today. Anthropic does not document that, so a Claude Code update can change it. If sw status starts failing, fall back to /login. Fixes are best effort.
  • Offline, sw cannot ask who owns the tokens. If you are offline and run /login at the same moment as a switch, the half-written login could be saved under the old email (spec/ configuration Offline_Login). Online, this cannot happen.
  • A saved login renews itself for about 28 days after its last use. An account unused for longer needs one /login; sw status warns three days ahead.
  • On macOS, a custom CLAUDE_CONFIG_DIR is switched through its own Keychain item, named the way Claude Code names it today (not documented).
  • On macOS, when the Keychain refused a write (for example over SSH), Claude Code saves the login to .credentials.json instead. acctswap then stops and says how to move the login back, because it can't tell which of the two Claude Code reads.

Layout

crates/acctswap-core   the rules: no file, Keychain or network access; ports, decision, protocol, proofs
crates/acctswap        the acctswap binary: Keychain / file / curl adapters, the hook, the commands
spec/                  the TLA+ model and its configurations

Licence

Either of MIT or Apache-2.0, at your option.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.2.317 Oct 20260

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

ACCESSIBLE
cj-ways/acctswap

Shared repository data · GitHub

License detected in repository
Apache-2.0
License branch / ref
main
Last push
17 Oct 2026
Archived
No
Contributor identities
— · partial collection

Public sponsor counts have not been collected. These signals belong to the repository owner.

Downloads & popularity

0count
Gauge · observed snapshots21 Oct 2026
10.50
2026-10-21: 0 · observed 2026-10-21T10:40:26.2837825Z
21 Oct21 Oct

github · 1 observations shown. Missing days are unknown.

View observed values
Date / source periodValueObserved at
2026-10-2102026-10-21T10:40:26.2837825Z

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 18:02 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:fd650ab4f689cdbcb4112abef446919788d79378425856ff7e97ac527ec1672a