/switch
/switch sessions and accounts
Move this conversation to another Claude account (the previous one, or one by name), or set up, add, remove, rename and list accounts.
- invocation user only
- prereqs Python 3.8+ (stdlib only) and Claude Code on PA…
- tested sandbox-tested
- suggested model · effort haiku · low
Install
Install just this, from a clone of the repo. Anything it needs comes with it.
./scripts/install-skills.sh --skill switch # bash, Git Bash, macOS, Linux
.\scripts\install-skills.ps1 -Skill switch # Windows PowerShell
Works on its own.
What it does
Runs several Claude Code accounts side by side, each with its own config dir, and moves a live conversation from one to another.
- Start Claude with
claude(the account you used last) orclaude-<name>. - Inside a session,
/switchcloses it and reopens the same conversation on your previous account,/switch <name>on a named one. /switch setupdoes the one-time setup from inside Claude./switch add|remove|rename|listmanages the accounts; new ones share skills, settings and history with the first.listshows who is logged in where and how much usage each account has left.- Before each switch it prints what ends or pauses (Remote Control, running subagents, background commands). It never asks.
- A statusline shows which account you're on and how much of its session and weekly limits is used, and warns you near the limit with the account that has the most room left.
Login, settings and transcripts belong to a config dir; some features belong to the claude.ai account that is logged in there.
Before every switch /switch prints a line for what ends or pauses (Remote Control, subagents, background commands). None of it stops the switch. Artifacts, routines and connectors (the last two items below) are not printed:
- Remote Control ends with the old Claude process.
- When it was on,
/switchsays so. - Run
/remote-controlon the target if you want it there.
- When it was on,
- Subagents. A subagent whose transcript (under the session's
subagents/folder) has no final reply yet counts as running, however long ago it last wrote.- The switch pauses it; after the relaunch,
SendMessageto its id resumes it with its history. - Explore and Plan agents are one-shot and can't be resumed.
- The switch pauses it; after the relaunch,
- Background Bash commands and monitors end with the old process and are not resumed.
- Artifacts published in this session and routines stay on the old account; republishing an artifact from the new one needs edit rights to it.
- claude.ai connectors and MCP OAuth follow the login: run
/mcpon the target to connect them there. The Chrome extension must be signed into the target's organisation.
Each switch also writes a resume note, ~/.claude-switch/requests/<launcher pid>.note.md.
- It holds the session, working dir, branch, last commit, live subagent ids and what to do about them.
- The launcher prints its path under the switch line and deletes it when it exits.
- Point the resumed conversation at it if you need it.
When to use
- One account hits its usage limit mid-task and you want to carry on with the same conversation on another.
- You keep a work and a personal login and want one command per account
instead of exporting
CLAUDE_CONFIG_DIRby hand. - You want a new account that sees the same skills, settings and conversation history as your first one.
How to invoke
First time, start to finish:
- Install the skills:
scripts/install-skills.sh(on Windows,scripts/install-skills.ps1). - Start Claude as usual and type
/switch setup workto name the account you're onwork(plain/switch setupcalls itmain).- Claude shows the shell profile and settings changes it would make and asks "Write these changes?". Nothing is written until you say yes.
- Called it
mainand want another name?/switch rename main work.
- Open a new terminal, because shell profiles only load in new shells (or reload with
source ~/.bashrc/. $PROFILE). Then runclaude(orclaude-work). - Inside that session, run
/switch add personal.- It creates
~/.claude-personal, links your first account's skills, settings and history into it, and reopens this conversation there. - Claude asks you to log in to the new account once.
- This switch-in only happens when Claude was started by the launcher (
claudeorclaude-<name>). Otherwise it only adds the account and tells you to start it withclaude-personalfrom a new shell.
- It creates
- From then on,
/switchflips back to the other account,claude-personalstarts on it directly, and/switch listshows both.
Claude not running, or you only use cmd? Run step 2 from a shell instead, with the installed copy of the script.
On Windows type python (or py) wherever these examples say python3: there python3 is usually the Microsoft Store stub.
python3 ~/.claude/skills/switch/scripts/claude_switch.py setup work
It prints the same diff and writes only after you type y; --dry-run prints it and writes nothing.
To register two accounts in one go:
python3 claude_switch.py setup --account work=~/.claude --account personal=~/.claude-personal
Already have ~/.claude-<name> dirs? python3 claude_switch.py migrate
(Windows: python claude_switch.py migrate) registers them (and
~/.claude as main) without touching their contents.
Then, in a new shell, run claude or claude-personal. Inside a session you have:
/switch [name]
/switch setup [name]
/switch add <name> [--dir DIR]
/switch remove <name> [--delete-dir]
/switch rename <old> <new>
/switch list
/switch theme <name> [dark|light|...|-]
Only you can run it; Claude never switches on its own. To take the
statusline and hook back out of settings.json:
python3 claude_switch.py setup --uninstall.
How it works
- Setup (
claude_switch.py setup) registers each--account NAME=DIRin~/.claude-switch/accounts.json(name, dir, colour, theme, last-used; written atomically). It also copies the script there. The wrappers call that copy, so updating or uninstalling the skill doesn't break your shells.setup <name>registers the config dir you're on ($CLAUDE_CONFIG_DIR, else~/.claude) as<name>. It refuses a name or dir that is already registered differently.- With neither, a first run registers the config dir you're on as
main. --dry-runprints the diff and writes nothing./switch setupruns that first, then--yesafter you agree in chat.- It then writes a block between
# >>> claude-switch >>>markers into each shell profile it finds (bash, zsh, fish, PowerShell 5.1 and 7) andclaude.cmd/claude-<name>.cmdshims for cmd. - It shows the unified diff and writes only after
y. - Rerunning replaces only its own block.
- Launcher.
claudeandclaude-<name>runclaude_switch.py run.- It starts the real
claudewith that account'sCLAUDE_CONFIG_DIR(unset for~/.claude, so~/.claude.jsonstays where Claude expects it),CLAUDE_SWITCH_LAUNCHER=<its pid>and the account name. - It records the account as last used.
- It starts the real
/switchrunsclaude_switch.py request [name] --session <this session id>.- With no name it picks the account this launcher came from, else the most recently used other one.
- It prints the pre-switch notes (see "What follows the account").
- It writes a resume note to
~/.claude-switch/requests/<launcher pid>.note.mdand the request torequests/<launcher pid>.json(target, session, the transcript's absolute path). - A trailing
yes(/switch work yes) is ignored. - Unknown names list the accounts, a session not started by the launcher gets the setup steps.
- Relaunch. The launcher sees its own request file, ends Claude (Windows: the whole process tree, MCP servers included), restores the terminal (raw mode, alt screen, bracketed paste, cursor), and starts the target account with
--resume <session id>.- It uses
--resume <absolute transcript path>instead when the target'sprojects/doesn't hold the session (an account with its own, unshared history). - The switch line is followed by the resume note's path.
- If that exits with an error within 10 s it prints one line and uses
--continue. - Other args you started Claude with (e.g.
--model) are not carried across a switch; only--resume/--continueare passed.
- It uses
- When Claude exits without a request, the launcher exits with its code. Ctrl+C goes to Claude, never the launcher.
add <name>creates~/.claude-<name>(or--dir) and links the first account's files and dirs into it.- Linked:
skills/,agents/,commands/,plugins/,projects/(a missing one is created empty there first),settings.jsonandCLAUDE.md. .credentials.jsonand.claude.jsonstay per account, so Claude asks the new one to log in.- Dirs are junctions on Windows and symlinks elsewhere; files are symlinks.
- An item already in the new dir is left as is.
- It picks a free colour, regenerates the shell files setup wrote and, inside a launched session, moves this conversation to the new account.
- Linked:
remove <name>unregisters it and regenerates the shell files; the dir stays unless you pass--delete-dirand confirm.- The skill always shows the
--dry-runplan first: which links get unlinked (their shared targets are kept) and what gets deleted. - Deleting unlinks every link and junction first and never walks through one.
- It refuses the account you're on, the last one, and the first one while others link into it.
- The skill always shows the
rename,listandmigrate.rename <old> <new>changes the name and the shell commands; the dir is not moved.listshows one line per account: colour, logged in or out, email, last seen session and weekly usage with time left, last use, dir, and which one you're on.- Logged in means the account's dir holds
.credentials.json. On macOS, where the login lives in the Keychain (never read), anoauthAccountin its.claude.jsoncounts instead. - The email comes from that
.claude.json(~/.claude.jsonfor~/.claude),-when there is none. - Usage comes from the statusline's cache:
-before the account has reported any,0%once a window has reset.
- Logged in means the account's dir holds
migrateregisters~/.claudeand every~/.claude-<name>dir holding.credentials.json,.claude.jsonorprojects/; it links nothing, and once setup has run it adds the statusline and hook to each one'ssettings.json.- Renaming the account you're on is fine: the launcher looks it up by its dir at the next switch.
- Statusline. Setup also writes a
statusLine(refreshed every 60 s), and the hook below, into every account'ssettings.json.- A
settings.jsonlinked between accounts is written once. An account whose dir doesn't exist yet is skipped (rerun setup after its first login). addandmigratedo the same for a new account with its own file;removetakes them out of a file no other account uses.- The statusline prints the account name in its colour, then
session 42% · 2h13m left week 18% · 4d6h leftfrom Claude Code'srate_limits.five_hour/seven_dayfields (-until Claude reports them; they only exist for Pro and Max plans). - A statusline you had before is never overwritten. Setup records it per file in the registry. The new statusline runs it with the same input and puts its output on the same line.
setup --uninstallputs it back. - Settings are backed up to
~/.claude-switch/backups/and written in place, so a linkedsettings.jsonstays linked.
- A
- Usage guard. The statusline caches each account's last seen usage in
~/.claude-switch/usage/<account>.json.- With two or more accounts, a
UserPromptSubmithook warns once when the account crosses 90% and once at 97% in a window:work at 91%: run /switch personal, naming the account with the most room (a window past its reset counts as empty).
- With two or more accounts, a
/switchat 100%. The same hook catches a typed/switchor/switch <name>, writes the launcher's request and blocks the prompt, so the switch makes no API call and works on an account that is out of usage.- Claude Code gives this hook the line exactly as typed, before the skill expands (hooks reference, section "UserPromptSubmit input"; blocking per "UserPromptSubmit decision control").
- It answers
/switch listtoo, with or without the launcher, so the list also needs no API call. setup,add,remove,renameandthemestill go through the skill.
- Theme and title.
/switch theme <name> <theme>gives an account its own Claude theme.- The launcher passes it as
--settings '{"theme":...}', merged with a--settingsyou passed yourself (yours wins on the same key). - It also sets the terminal tab title to
claude: <name>at each start and switch, and prints the switch line in the target account's colour.
- The launcher passes it as
Launcher-only: the switch itself, the theme, the tab title and the rename pickup need Claude started with claude or claude-<name>.
- The statusline and the guard work in any session on a registered account.
/switchoutside a launcher prints the setup steps.
Flow
Design principles
- One launcher per terminal, one request file per launcher. Each
launcher only watches
requests/<its pid>.json, so two terminals switching at once never pick up each other's request (the older approach shared a single file and climbed the process tree). - Shells stay thin. Every shell gets a generated one-line wrapper per account that calls the same stdlib Python launcher with an absolute interpreter path; all logic lives in one script you can test.
- Show before writing. Setup prints the exact profile diff and asks; later account commands only rewrite the marked block in the files setup recorded, and print each path they change.
- Wrap, never replace. Setup adds one statusline and one hook to
settings.json, keeps a statusline you had and runs it, and--uninstallremoves only what it added. - Deleting never follows a link. Shared history lives behind every
account's
projects/link, so a delete that recursed through one would wipe it for all accounts.
Shared context for these principles: Matt Pocock's video.
Use cases
- Out of usage on one account:
/switchand keep going in the same conversation on the other; the hook does it without an API call. - The guard says
work at 91%: run /switch personalbefore you run out. - Two terminals, two accounts, both switching back and forth all day.
- A new login that should see your skills and history from the start:
/switch add work, log in, carry on. - A cmd or PowerShell user who wants the same
claude-<name>commands as a bash user.
Tips
- Accounts made with
addshareprojects/, so the target resumes the session by id.- For accounts registered by
setupormigrateit isn't there, so the launcher passes the transcript's absolute path instead (a Windows path with spaces as its short 8.3 name). - If that resume fails too, it falls back to
--continue(the target's own latest conversation).
- For accounts registered by
- Windows without Developer Mode can't make file symlinks:
addhardlinkssettings.jsonandCLAUDE.mdinstead (or copies across drives) and warns.- Claude rewrites
settings.jsonby replacing the file, which splits a hardlink into a separate copy. - Turn on Developer Mode (Settings, System, For developers) before
addto keep it shared.
- Claude rewrites
- bash on macOS reads
~/.bash_profilefor login shells; source~/.bashrcfrom it, or pass--profile bash=~/.bash_profile. - cmd: put
~/.claude-switch/binfirst onPATH. The launcher never picks its own shims when it looks for the realclaude. --profile SHELL=PATH(repeatable;bash,zsh,fish,powershell,pwsh,cmd) writes only those targets, for a non-default profile.- Rerun setup after moving Python (wrappers embed its absolute path) or updating the skill (refreshes the script copy the wrappers call).
- PowerShell with execution policy
Restricted(the Windows default) never loads your profile, soclaude-<name>is missing: runSet-ExecutionPolicy -Scope CurrentUser RemoteSignedonce. - Setup refuses a profile that isn't UTF-8 (PowerShell 5.1's
Set-Contentwrites ANSI): save it as UTF-8 and rerun. - An old
~/.claude-switchfile from earlier scripts is moved aside to~/.claude-switch.stale-<time>. - On Windows Claude Code runs the statusline and hook commands through Git Bash when it is installed, else PowerShell (statusline docs, "Windows configuration"). Setup writes them with forward slashes and short (8.3) names for paths with spaces, so the same line works in both.
- An account with its own
settings.json(registered bysetupormigrate, or an existing fileaddleft as is) gets the statusline and the hook in that file too, so/switchfrom it needs no API call either. - Theme names are Claude Code's:
dark,light,dark-daltonized,light-daltonized,dark-ansi,light-ansi.-clears it.
Example
before switching work -> personal:
dies: background Bash commands and monitors, if any (Explore/Plan agents are one-shot)
switching to 'personal': this session ends now and resumes there.
>> claude-switch: work -> personal
resume note: ~/.claude-switch/requests/41236.note.md
The same conversation reopens on personal.
/switch nopeinstead printsunknown account 'nope'. accounts: work (current), personaland does nothing./switch personal now(anything after the name butyes) printsusage: /switch [name].
/switch list
work cyan logged in sam@example.com session 42% (2h13m left) week 18% (4d6h left) last used 2026-10-17 09:12 UTC /home/sam/.claude (current)
personal magenta logged out - session - week - last used never /home/sam/.claude-personal
Related
session-handoff: when the conversation is too long to carry over, hand off instead of switching.sitrep: check what was running after a switch.
Prereqs
Python 3.8+ (stdlib only) and Claude Code on PATH. Run /switch setup once.
Sample workflows
- Run the backlog unattended
you → lanes → you → meta-orchestrator → planner → manager → implementer → verifier → you → switch → remind → progress-report → you
Install just this one:./scripts/install-skills.sh --workflow unattended-backlogor.\scripts\install-skills.ps1 -Workflow unattended-backlog - Push a score up until it plateaus
score-loop → you → spend-gate → you → you → switch → remind → handoff-auto → you
Install just this one:./scripts/install-skills.sh --workflow climb-a-scoreor.\scripts\install-skills.ps1 -Workflow climb-a-score - Keep going on another account
you → switch → you → switch → sitrep → remind → you
Install just this one:./scripts/install-skills.sh --workflow usage-limitor.\scripts\install-skills.ps1 -Workflow usage-limit
Use cases & tips from the community