Skip to main content

claudehop — hop Claude Code between accounts

claudehop

Hop Claude Code between several Claude accounts without logging in again.
Personal Max account in one terminal, work Team seat in another — two separate usage pools, one machine, no browser round-trip.

PyPI CI Licence: MIT Python 3.9+ No dependencies

Install · Changelog · Security


Switching accounts

Run hop, press a number.

$ hop
      NAME  EMAIL           PLAN
1.    home  me@gmail.com    max
2. *  work  me@company.com  team

hop to which? [1-2, Enter to stay] 1
switched to home (me@gmail.com, max)

Then start a new claude. That's it — that's the whole tool.

If you'd rather not read a menu, hop home goes straight there. Sessions you already have open keep the account they started with; only new ones pick up the change.

Install

pipx install claudehop              # or: pip install --user claudehop
eval "$(claudehop shell-init)"      # the `hop` alias + tab-completion

Put that eval line in your ~/.bashrc or ~/.zshrc and open a new terminal. hop and claudehop are the same command.

Or from a clone, if you'd rather have it symlinked into ~/.claude with the shell glue written for you:

git clone https://github.com/psychofict/claudehop.git
cd claudehop
./install.sh          # symlinks into ~/.claude, adds one line to your rc file

./install.sh --copy installs copies instead of symlinks, --no-rc skips the shell wiring, --uninstall reverses it. None of them ever touch ~/.claude/accounts/, where the credentials live.

Requires Python 3.9+ and Claude Code. Linux and macOS. One module, no dependencies.

Adding an account

hop add work

Quit your other claude sessions first — add will stop and tell you if you haven't, because a session left running can write its own token back into the credential store mid-login and you'd end up with the wrong account saved under that name.

add then starts claude with no login so you can /login, and saves whatever that produces when you exit with /exit. Paste the login URL into a private browser window. Your normal browser is already signed in as one of your other accounts and will authorise that one without asking.

If the login produces nothing — you changed your mind, you hit Ctrl-C — your previous credentials come back. If it does produce a login, that login is saved even if the terminal dies on the way out.

Already logged in by hand? hop save work names whatever is live right now.

Everything else

hop whoami           # who am I right now (asks the API)
hop --long           # add token expiry and save dates to the listing
hop list --verify    # check every saved token against the API
hop rm <name>        # delete a saved account (does not log you out)
hop rename <a> <b>
hop doctor           # check the setup; --fix repairs what it can
hop shell-init       # shell glue for a pip install: alias + tab-completion

--json on list, whoami, active and doctor gives machine-readable output with no secrets in it, for scripts and statuslines. hop active prints just the active name with no network call. hop use <name> is the long spelling of hop <name>, and hop sync writes the live login back to its own file.

How it works

Claude Code keeps the live login under the claudeAiOauth key of its credential store. This tool keeps one saved copy of that block per account in ~/.claude/accounts/<name>.json and swaps the active one in and out. The mcpOAuth key in the same store — your Vercel/Neon/etc. MCP logins — is left alone, so switching accounts doesn't sign you out of anything else.

credential store
Linux ~/.claude/.credentials.json
macOS login keychain item Claude Code-credentials, falling back to the file

The backend is detected from whichever one currently holds a login. Force it with CLAUDE_HOP_BACKEND=file or =keychain if you need to.

Nothing else needs patching. Account identity in ~/.claude.json (oauthAccount) is re-fetched from the API by Claude Code at startup: put a bogus email in there, start a session, and it comes back corrected. So swapping the credential is the whole job.

Identity, plan and token checks come from GET /api/oauth/profile with the account's own bearer token. Set CLAUDE_HOP_OFFLINE=1 to skip every API call.

Two details that make or break it

Access tokens rotate. Claude Code refreshes them every few hours and writes the new one straight into the credential store. A switcher that identifies the active profile by comparing token values therefore stops recognising it after the first refresh — and then loses the refreshed token when you switch away. So the active profile is tracked in accounts/active and confirmed against the account UUID from the API, and every switch writes the live block back to its profile before loading the next one.

A login you can't identify is stashed, never dropped. If the live credentials match no saved profile (you ran /login by hand, say), switching saves them under a name derived from the account's email first. You can always get back to a session you'd otherwise have to re-authenticate.

Gotchas

  • Switching affects new claude processes. Sessions already running keep the account they started with, and will rewrite the credential store when their token refreshes — which can silently undo a switch. claudehop prints the PIDs it finds; quit them for a clean switch.
  • /login opens your default browser, which is already signed in as somebody. Paste the URL into an incognito window to authenticate as a different account.
  • ANTHROPIC_API_KEY and CLAUDE_CODE_OAUTH_TOKEN in the environment override the saved login entirely. whoami and doctor warn when either is set.
  • --long showing expired (auto-renews) under TOKEN is normal — the access token is short lived and Claude Code renews it from the refresh token. list --verify prints stale (renews) for the same reason. What actually matters is the refresh token; see below. This is why the default listing doesn't show either of them.
  • Each account still has its own rate limits and its own terms. This moves your own logins between your own terminals; it is not a way to pool quota.

Every account needs a real login about once a month

The refresh token is good for roughly 30 days from the /login that issued it, and using the account does not extend it. Measured 2026-08-06 across four accounts: one had its access token reissued that morning and its refresh window still ended 30 days after its first login, not 30 days after the refresh. So this is a hard monthly expiry per account, and nothing on this side can lengthen it — it's set by the OAuth server. claude setup-token is not a way around it either; those tokens expire too, and carry inference scope only.

With several accounts the dates drift apart and you get a browser round-trip per account per month. Logging in early resets the whole 30 days, so the cheap move is to do them all on the day the earliest one comes due — after that they share one date and it's one sitting a month. doctor works this out for you:

$ hop doctor
  accounts     /home/you/.claude/accounts (4 saved)
  active       work
  re-login     by 2026-08-30 (work); the other 3 by 2026-09-04
               windows are ~30d from login and do not slide, so re-login all 4 on
               2026-08-30 and they collapse to one date

You also get a per-account warning starting 14 days out, and doctor --json carries the same thing under reloginPlan if you want to hang a reminder off it.

Why not claude setup-token

The obvious approach is a long-lived token per account exported as CLAUDE_CODE_OAUTH_TOKEN. It doesn't hold up:

  • those tokens carry inference scope only — /api/oauth/profile answers 403 OAuth token does not meet scope requirement, so you can't tell whose token you're holding or whether it's still good;
  • they expire, and a dead one looks exactly like a live one until a request fails;
  • it's per-shell, so every terminal has to be primed before claude starts.

Swapping the real credential block avoids all three and matches what Claude Code already does to itself.

Security

Saved credentials are real, live Claude logins. accounts/ is 700, every profile is 600, and writes are atomic. Nothing is ever sent anywhere except api.anthropic.com to resolve an email and plan. See SECURITY.md for the threat model and how to report a problem.

Files

claudehop.py                 the tool (python3, stdlib only)
shell/claudehop.sh           PATH, tab-completion, back-compat aliases
extras/statusline-snippet.sh show the active account in the Claude Code statusline
install.sh                   symlink/copy into ~/.claude, wire up the rc file
pyproject.toml               packaging: one module, no dependencies, two commands
test/test-switch.sh          99 checks against a throwaway config dir, no network
assets/                      logo, icon, cover and social preview (svg sources + png)

Tests

./test/test-switch.sh

Runs entirely inside a temp dir with fake credentials — it never reads or writes a real account, and never touches the network. Covers the swap, mcpOAuth preservation, token rotation, the stash path, concurrent switches, the macOS keychain backend (through a stand-in security), add rolling back a failed login, add surviving a teardown after a successful one, the refresh race, JSON output, table layout, housekeeping and file permissions.

Contributing

Issues and pull requests welcome — see CONTRIBUTING.md. If claudehop saved you a browser round-trip this morning, a ⭐ on GitHub helps others find it.

Licence

MIT. Not affiliated with, endorsed by, or sponsored by Anthropic. "Claude" and "Claude Code" are trademarks of Anthropic, PBC, used here only to say what this works with.


Made by Ebenworks

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

claudehop_cli-1.4.0.tar.gz (45.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

claudehop_cli-1.4.0-py3-none-any.whl (23.7 kB view details)

Uploaded Python 3

File details

Details for the file claudehop_cli-1.4.0.tar.gz.

File metadata

  • Download URL: claudehop_cli-1.4.0.tar.gz
  • Upload date:
  • Size: 45.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for claudehop_cli-1.4.0.tar.gz
Algorithm Hash digest
SHA256 c57bbe5c533239c572a76d13f3ab248c7ca942988041885296a51ac3b921d4ce
MD5 e89cfba6303fe0be59c3a7359c3db6a9
BLAKE2b-256 a1a9758f30f95200be4bce71a7069d5a5742e6078187ef32fe8b34ff6af70a53

See more details on using hashes here.

File details

Details for the file claudehop_cli-1.4.0-py3-none-any.whl.

File metadata

  • Download URL: claudehop_cli-1.4.0-py3-none-any.whl
  • Upload date:
  • Size: 23.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for claudehop_cli-1.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cfb11bbc7a7aa5700fd2cf05ef6e7b71c9b86fc68918c50a808015f0313baa93
MD5 e44eb20706c5188debe2d616fea9696d
BLAKE2b-256 2ffeb6be54583b1442e0de3e6272f272816832a19869450434a663d31e6c1ea3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page