orvi2014/hatswap

Wear a different hat for each client: run several Claude Code accounts side by side, move a live conversation between them, pin folders to accounts, and hand work to Codex.

Shell

1

25 commits

updated Oct 13, 2026

See the code

See what people are saying

README

hatswap

Every client's Claude Code account, signed in at once. Swap mid-conversation.

test License: MIT Platform: macOS | Linux

hatswap keeps each Claude Code account signed in at the same time and moves work between them — by resuming a conversation, or by starting a fresh session from a compact, repository-aware handoff brief. The same brief can restart an overgrown session on the same account, or seed an OpenAI Codex session.

hatswap: the account picker showing each account's email and 5-hour and weekly usage, setting the default and pinning the folder from it, claude starting on the pinned account inside the folder and on the default outside it, switching a conversation to another account, and handing the task to Codex

Recorded in a sandbox with example accounts and a stand-in for claude; reproduce with bash demo/setup.sh && vhs demo/demo.tape.


Contents


Features

  • Concurrent accounts. Every account stays signed in; run two accounts in two terminals at once.
  • Shared environment. Settings, agents, skills, plugins, hooks, rules, output styles, keybindings, memory and conversation history are shared across accounts. Only the login differs.
  • Switch in place. switch moves the current conversation to another account. In tmux (including desktop apps that host tabs in tmux), Terminal.app and iTerm2, the same tab restarts on the new login with its history intact.
  • Default account. default (or --default) picks the account plain claude starts on; setup-shell installs the one-line hook for zsh, bash or fish.
  • Per-folder accounts. pin acme in a client's folder and every claude started there uses that client's account.
  • Account picker. Type hatswap on its own for a list of every account with its email, limits, default and pin; pick one with the arrow keys and start Claude, set the default or pin the folder with one key.
  • Usage per account. usage shows every account's 5-hour and weekly limits in one table, the numbers /usage shows inside Claude.
  • Account in the status line. statusline --install puts the account and email at the bottom of every session, ahead of any status line you already have.
  • VS Code, Cursor and Claude Desktop. vscode acme makes the Claude extension use acme's login in that folder; desktop acme opens Claude Desktop signed in as acme, in its own window.
  • Bots follow the default. exec (or the claude/codex shims from shim) looks the account up on every call, so a long-running bot that starts claude -p per job moves to a new default without a restart.
  • Codex per account. Each account can have its own Codex login; handoff codex uses the one that belongs to the session's account.
  • Private history. Accounts can keep their conversations and memory to themselves, for client work that must stay separate.
  • One-line install and update. The installer sets up PATH, the shell hook and the slash commands; outdated slash commands are flagged.
  • Session handoff. Continue a conversation on another account with --resume, or fork a copy of it.
  • Brief handoff. Start a fresh session from a structured summary instead of replaying the full history.
  • Refresh. Restart a session that has grown too long, on the same account, from its brief.
  • Repository-aware briefs. Branch, uncommitted changes, recent commits and your open pull requests are collected at handoff time and treated as ground truth.
  • Review before launch. Open the brief in your editor and correct it before the new agent sees it.
  • In-session /handoff and /switch. A running Claude Code session writes its own brief, from full context, at no extra cost, or moves itself to another account.
  • Codex handoff. Seed an OpenAI Codex session with the same brief.
  • Live-session protection. Refuses to resume a conversation that is still open in another window.
  • Secret redaction. Passwords, API keys, tokens and private keys are removed before a transcript is summarised.

Requirements

Operating systemmacOS or Linux
ShellBash 3.2 or later (the macOS default works)
RuntimePython 3
Claude Codeclaude on your PATH
Optionalgh for open pull requests in briefs · codex for handoff codex · tmux for switching a session in place

Setup

1. Install

curl -fsSL https://raw.githubusercontent.com/orvi2014/hatswap/main/install.sh | bash

This installs hatswap to ~/.local/bin, adds that folder to your PATH if needed, installs the default-account shell hook and the /handoff and /switch commands. Open a new terminal afterwards. Every change is marked and reversible (see Uninstall).

Options go after bash -s --, for example curl -fsSL …/install.sh | bash -s -- --no-shell:

OptionEffect
--no-shellDo not edit shell startup files (no PATH line, no default-account hook).
--no-commandsDo not install /handoff and /switch.
--bin-dir <dir>Install somewhere other than ~/.local/bin.

Update later with hatswap update. It also refreshes the slash commands.

Install by hand
mkdir -p ~/.local/bin
curl -fsSL https://raw.githubusercontent.com/orvi2014/hatswap/main/bin/hatswap \
  -o ~/.local/bin/hatswap
chmod +x ~/.local/bin/hatswap

If ~/.local/bin is not on your PATH, add it to your shell profile.

Install from a clone instead
git clone https://github.com/orvi2014/hatswap.git
ln -s "$PWD/hatswap/bin/hatswap" ~/.local/bin/hatswap

git pull in the clone (or hatswap update) then updates the command.

2. Verify

hatswap list

Your existing Claude Code login appears as main. It is used as-is and never modified.

3. Add a second account

hatswap add work

A browser window opens. Sign in with the other account — if the browser is already signed in to your main account, use a private window. Pass --email [email protected] to prefill the address, or --console to use an Anthropic Console (API billing) login.

hatswap list
#   main   [email protected]   (~/.claude)
#   work   [email protected]

4. Let plain claude follow a default account (optional)

hatswap setup-shell          # adds a guarded hook to your shell's startup file
hatswap default work         # plain `claude` now starts on "work"

setup-shell detects zsh, bash or fish (override with --shell), is safe to run twice, and is undone with setup-shell --remove. Open a new terminal afterwards.

5. Enable /handoff inside Claude Code (optional)

hatswap install-command

This adds ~/.claude/commands/handoff.md and switch.md, which every account shares. In any Claude Code session you can then run /switch work, /handoff work, /handoff codex or /handoff.

Upgrading from claude-account

Formerly claude-account. Renamed in 0.5.0 to avoid confusion with an unrelated project of the same name. Old links redirect here, and the claude-account command keeps working as an alias.

claude-account update        # installs hatswap next to it; claude-account becomes an alias

Or re-run the installer. Nothing else changes: your accounts, logins, briefs, default account, pins and shell hook keep working. The data folder is still ~/.claude-accounts, because Claude Code ties each account's Keychain login to that path; moving it would sign every account out. Older names are still honoured:

NewStill accepted
hatswapclaude-account (alias)
.hatswap pin files.claude-account (pin converts it when you re-pin)
HATSWAP_DIRCLAUDE_ACCOUNTS_DIR
HATSWAP_KEEP_AUTH_ENV, HATSWAP_SWITCH_DELAYCLAUDE_ACCOUNT_KEEP_AUTH_ENV, CLAUDE_ACCOUNT_SWITCH_DELAY
# >>> hatswap >>> hook block# >>> claude-account >>>

Run hatswap install-command once to update /handoff and /switch (they now call hatswap); hatswap list reminds you.

Uninstall

hatswap setup-shell --remove # if you added the shell hook
hatswap remove work          # repeat for each added account (also signs out its Codex login)
rm -f ~/.local/bin/hatswap ~/.local/bin/claude-account ~/.claude/commands/handoff.md ~/.claude/commands/switch.md
rm -r ~/.claude-accounts            # saved briefs

The installer's PATH line is marked # hatswap PATH in your shell profile; delete that line too.

Usage

Work on another account

hatswap use work             # or simply: hatswap work
hatswap main                 # back to the main account (~/.claude)

Run each in its own terminal to use both at once. hatswap whoami shows which account the current shell or Claude session is on, and which one a new claude starts on (inside Claude Code, ! hatswap whoami or /status).

Switch the account of the current conversation

Inside Claude Code:

! hatswap switch work            # or /switch work (after install-command)
! hatswap switch work --default  # also make "work" the default for new sessions

A running Claude Code process cannot change its login, so switch restarts it: the same conversation (full history, same ID) resumes on the other account, in the same tab:

Where the session runsWhat happens
tmux, including desktop apps that host each tab in tmuxThe pane restarts in place, keeping its original flags, hooks and MCP config.
Terminal.app or iTerm2 (macOS)Claude exits and the resume command is typed into that tab. macOS asks once to let your terminal control Terminal/iTerm2.
Anything elseType /exit, then run hatswap switch work in that terminal.

Run from a different terminal, switch restarts the session in its own tab when it can, and otherwise closes it there and resumes it in the terminal you ran it from.

Choose the default account

hatswap default work         # plain `claude` now starts on "work"
hatswap use work --default   # set it and start a session
hatswap default main         # back to ~/.claude
hatswap default              # show the current default

The first time you set a default, hatswap offers to add a one-line hook to your shell's startup file (~/.zshenv, ~/.bash_profile / ~/.bashrc, or fish conf.d) so plain claude follows it. For zsh it goes in ~/.zshenv rather than ~/.zshrc so that sessions an app reopens after a restart (with zsh -l -c claude …) also get it; an older hook in ~/.zshrc moves itself there the next time a terminal opens. The hook reads the default on every call, so changing it later needs no reload. An explicit CLAUDE_CONFIG_DIR always wins.

Pin a folder to an account

cd ~/clients/acme && hatswap pin acme   # plain `claude` here, and in every subfolder, starts on "acme"
hatswap pin                             # show which pin applies here
hatswap pin main                        # this folder uses main, whatever the default
hatswap pin --clear                     # remove this folder's pin

pin writes a one-line .hatswap file. Inside a git repository it is added to the repository's local ignore (.git/info/exclude), so it is never committed. The nearest pin wins, so a subfolder can pin a different account. Order of precedence for a plain claude: an explicit CLAUDE_CONFIG_DIR, then the nearest pin, then the default account, then main. whoami shows which one applies and why, and refresh and handoff codex follow it too. Pins work through the same shell hook as the default.

Move a task to another account

hatswap sessions             # conversations in this folder, and where each is open
hatswap handoff work --brief # fresh session on "work", seeded with a brief
hatswap handoff work         # or resume the full conversation (close the original first)

Restart a session that has grown too long

hatswap refresh              # brief + fresh session on the same account
hatswap refresh --edit       # review the brief in $EDITOR first

Hand off from inside a running session

/handoff work

Claude writes the brief from its own context, saves it, and prints the command to continue — for example hatswap handoff work --from ~/.claude-accounts/.briefs/….md.

Continue in Codex

hatswap handoff codex

Give each account its own Codex login

hatswap codex-login acme      # sign "acme" in to its Codex (ChatGPT/OpenAI) account
hatswap codex acme            # start Codex on acme's login
hatswap handoff codex         # uses the session's account's Codex login when it has one
hatswap handoff codex --as acme

Each account's Codex login lives in its own CODEX_HOME (~/.claude-accounts/<name>/codex). config.toml, AGENTS.md, prompts, rules, skills and plugins are shared with ~/.codex; the login, history and sessions are not. main uses ~/.codex. add acme --codex signs in to both at once, and list shows each account's Codex email.

Keep a client's conversations to its own account

hatswap add acme --private    # new account with its own history and memory
hatswap private acme          # or convert an existing one
hatswap private acme --off    # share history again (only while its own history is empty)

By default every account shares conversation history. A private account keeps its conversations and memory in its own projects/ folder, so other accounts never read them; settings, skills and MCP servers are still shared. sessions, brief and handoff see private conversations too. When you deliberately hand one to another account, it is copied into that account's history and the command says so.

Pick an account from a list

Type hatswap with nothing after it in a terminal:

hatswap ─ new sessions in ~/clients/acme start on acme

   main     [email protected]    5h    0% ░░░░░░  week  100% ██████
 ▸ acme     [email protected]        5h   10% █░░░░░  week   27% ██░░░░  ★ default  codex

 ↑↓ choose   ⏎ start claude   d default   p pin folder   s continue last chat   c codex   l sign in   q quit

It draws in place rather than taking over the screen, so it behaves in tmux and terminal apps. Scripts, pipes and ! inside Claude still get the help text; HATSWAP_NO_PICKER=1 turns it off.

See every account's limits

hatswap usage            # all accounts
hatswap usage acme       # one; --json for scripts
ACCOUNT        EMAIL                          5-HOUR  RESETS      WEEK  RESETS
main           [email protected]                   0%              100%  Fri 14:00
acme           [email protected]                       5%  in 0h44m     23%  Sat 16:00

The numbers come from the same endpoint Claude's /usage reads. It is not a public API, so it may change; results are cached for 3 minutes because it is rate-limited. hatswap reads each login from the Keychain (or .credentials.json on Linux) only to make that request: tokens are never printed, written or refreshed. An expired one says so; starting Claude on that account refreshes it. hatswap never switches accounts for you.

Show the account in Claude's status line

hatswap statusline --install   # ⎈ acme · [email protected]  │ <your existing status line>
hatswap statusline --remove    # put back what you had

All accounts share settings.json, so one install covers every account. A status line you already had keeps running, after the account.

Use an account in VS Code, Cursor or Claude Desktop

cd ~/clients/acme
hatswap vscode acme        # the Claude extension uses acme in this folder
hatswap vscode             # same, using this folder's pin or the default
hatswap vscode --clear
hatswap desktop acme       # Claude Desktop as acme, in its own window

vscode sets CLAUDE_CONFIG_DIR in the folder's .vscode/settings.json (claudeCode.environmentVariables), keeping your other settings, and adds the file to the repository's local ignore. Reload the window afterwards and open that folder itself, not a parent. For main it removes the entry, because Claude finds main's login only when the variable is unset. A settings.json with comments is left alone and the line to add is printed. The extension's own session list may still show ~/.claude conversations (anthropics/claude-code#30538).

desktop starts a separate Claude Desktop with its own profile folder (~/.claude-accounts/<name>/desktop), so each account has its own window and login. Sign in once; it stays signed in. desktop main opens the app as usual.

Run bots and scripts on the current default

The shell hook only covers claude typed in a terminal. A bot started by launchd, cron or pm2 runs the claude binary directly, so it stays on whatever account it started with. Route it through hatswap instead:

hatswap exec claude -p "summarise today's issues"   # runs on the pin, then the default, at call time
hatswap shim                                        # writes ~/.claude-accounts/bin/claude and codex

Point the bot at ~/.claude-accounts/bin/claude (or put ~/.claude-accounts/bin first on its PATH). Each call looks up the account again, so hatswap default acme applies to the bot's next call, with no restart. The lookup uses the bot's working folder, so a pinned folder still wins. To keep one bot on one account whatever the default is, set HATSWAP_ACCOUNT=acme in its environment (or use exec --account acme).

exec ignores a CLAUDE_CONFIG_DIR the bot inherited, because following it would keep the bot on its old account. It clears ANTHROPIC_API_KEY and the other login variables for named accounts, quietly, and sets CODEX_HOME when the account has its own Codex login. A process that is already running keeps the account it started on: a bot that holds one Claude session open for hours picks up the new default only when it next starts claude. Keep the shim folder off your interactive PATH; the shell hook is the right tool there.

Work with saved briefs

hatswap briefs                          # list, with each brief's goal
hatswap handoff work --from <file>      # start any account from a saved brief
hatswap brief --edit                    # write, review and print a brief without switching

Command reference

CommandDescription
add <name> [--email <addr>] [--console] [--private] [--codex]Create an account profile and sign in. --private keeps its history to itself; --codex also signs it in to Codex.
private <name> [--off]Give an existing account its own history and memory, or share again.
codex-login <name|main>Sign an account in to its own Codex login.
codex <name|main> [codex args…]Start Codex on that account's Codex login.
use <name> [--default] [claude args…]Start Claude Code on an account. hatswap <name> is equivalent. --default also makes it the default.
(no command)In a terminal: the account picker. Otherwise the help text.
exec [--account <name|main>] <command> [args…]Run a command (claude, codex, a bot) on the account a new session would get now: --account, then HATSWAP_ACCOUNT, then the folder pin, then the default. No terminal needed.
usage [name…] [--json]Each account's 5-hour and weekly limits and reset times. Read-only.
statusline [--install|--remove]Show the account in Claude Code's status line, keeping an existing one.
vscode [name|main|--clear]Make the Claude extension in VS Code or Cursor use that account in this folder.
desktop [name|main]Open Claude Desktop signed in to that account, in its own window (macOS).
shim [--remove]Write (or remove) claude and codex wrappers in ~/.claude-accounts/bin that run through exec.
main [claude args…]Start Claude Code on the main account (~/.claude).
switch <target> [id] [--default] [--yes]Continue a conversation on another account, restarting it in its own tab (tmux, Terminal.app, iTerm2) or resuming it here.
default [name|main]Show or set the account plain claude starts on.
pin [name|main|--clear]Pin the current folder and its subfolders to an account, show the pin, or remove it.
setup-shell [--remove] [--shell zsh|bash|fish]Install or remove the hook that makes plain claude follow the default.
shell-init [zsh|bash|fish]Print that hook.
listList accounts and the email each is signed in as.
whoamiShow the account this shell or session is on, and the one new sessions start on.
--versionPrint the version.
login <name>Sign an account in again.
remove <name>Sign out and delete a profile; shared files are kept.
sessions [n]Recent conversations in the current folder and where each is open.
handoff <target> [id] [--fork]Resume a conversation on another account.
handoff <target|codex> [id] --brief [--as <name>]Start a fresh session there from a brief. For Codex, --as picks whose Codex login to use.
refresh [id]Brief and fresh session on the same account.
brief [id]Write and print a brief without switching.
briefs [n]List saved briefs.
install-command [--force]Install or update the /handoff and /switch commands. --force also replaces files it did not write.
updateUpdate hatswap (a clone is updated with git pull) and its slash commands.

<target> is an account name or main. Without an id, the most recent conversation in the current folder is used; IDs are listed by sessions.

Brief options (for handoff --brief, refresh and brief):

OptionEffect
--editOpen the brief in $VISUAL / $EDITOR before it is used.
--from <file>Use a saved brief instead of writing a new one.
--model <name>Model that writes the brief (default sonnet).
--no-repoLeave out repository state.

Commands that start Claude Code need a terminal; they will not run through Claude Code's ! prefix. switch is the exception.

When the installed /handoff or /switch is older than the current templates, list, whoami and the handoff commands print a one-line note to run hatswap install-command.

Handoff modes

--resume (default)--fork--brief / refresh
New session starts withthe full conversationa copy of the full conversationa structured brief
Context loadedentire historyentire historytypically 1–3k tokens
Original sessionmust be closedmay stay openmay stay open
TargetsClaude CodeClaude CodeClaude Code or Codex
Fidelityexactexactsummarised, then verified against the repository

Use --resume when exact continuity matters. Use a brief for long sessions, when the original is still running, or when moving to Codex. In testing, a 17 MB compacted session produced a brief of about 7 KB.

The handoff brief

A brief is Markdown with fixed sections:

SectionContents
GoalWhat the user asked for, in their words where possible
Done (verified)Completed work with evidence: pull requests, commits, deploys, test results
In flightThe step in progress when the session stopped
Decisions and constraintsChoices and rules the user set, quoted when strict
Tried and rejectedApproaches that were tried or considered and dropped, with the reason, so they are not repeated
Key pointersRepositories, branches, files, commands, URLs and IDs
Open questions / blockersAnything blocked or waiting on the user
Next stepOne or two concrete first actions

The receiving agent is told to verify the repository before relying on the brief, and to confirm with the user before any destructive or outward-facing action.

Two ways to write one

hatswap brief / --brief/handoff inside Claude Code
Written bya separate claude -p callthe running session itself
Inputcondensed transcript + repository statefull live context + repository state
Billed tothe account receiving the handoffthe current account
Works after the session has closedyesno

How the transcript is condensed. The input reflects what the model actually had in context: the latest compaction summary, the messages Claude Code retained verbatim when it compacted, and everything after. It keeps user messages, assistant replies, one-line tool calls and tool errors, and drops side chains, system reminders and tool output. Repository state is appended as a block the model treats as ground truth. The claude -p call uses a dedicated system prompt with tools, MCP servers and project settings disabled.

Briefs are saved to ~/.claude-accounts/.briefs/ with mode 600.

How it works

Hand-drawn diagram: a shared ~/.claude is symlinked into the main and work accounts, each with its own login; handoff --brief condenses an old session into a ~7 KB brief that seeds a fresh session on another account, the same account (refresh), or Codex

  • Isolation. Each account is a separate CLAUDE_CONFIG_DIR under ~/.claude-accounts/<name>. Claude Code stores one login per configuration directory — in the macOS Keychain, or in the directory itself on Linux — so accounts never overwrite each other. The default account runs with CLAUDE_CONFIG_DIR unset.
  • Sharing. settings.json, settings.local.json, CLAUDE.md, keybindings.json, agents/, skills/, plugins/, commands/, hooks/, rules/, output-styles/ and projects/ are symlinked from ~/.claude. User-scope MCP servers are copied once when an account is added.
  • Session discovery. Conversations are read from the shared projects/ directory. Open sessions are detected from each account's session registry and confirmed by process ID.
  • Switching. A running Claude Code process cannot change its login, so switch restarts it with --resume under the other account. If the session's registry entry names a tmux pane, the restart is scheduled through the tmux server (respawn-pane), so it works from inside the session and keeps the pane's original start command. Otherwise the session is asked to exit (SIGTERM) and resumed in the terminal that ran switch.
  • Private history. A private account has a real projects/ folder instead of the shared symlink, and a .private marker so it is never re-linked. Session discovery scans every distinct history folder.
  • Codex. Codex reads its login from $CODEX_HOME; each account gets ~/.claude-accounts/<name>/codex, with configuration symlinked from ~/.codex. The Codex email shown by list is read from the login's ID-token claims; tokens are never printed or copied.
  • Default account and pins. The default is a name in ~/.claude-accounts/.default; a pin is a .hatswap file holding an account name. The shell hook defines a claude function that, on every call, looks for the nearest .hatswap from the current folder upwards, falls back to the default, and sets CLAUDE_CONFIG_DIR only when it is not already set. It uses shell builtins for the folder walk, so it adds no noticeable start-up time.

Security and privacy

  • No credentials are read, copied or modified; sign-in is handled entirely by Claude Code.
  • ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN and CLAUDE_CODE_OAUTH_TOKEN override a login, so they are cleared (with a one-line note) whenever Claude starts on a named account, including through the shell hook. Your shell keeps them, and main is left alone. Set HATSWAP_KEEP_AUTH_ENV=1 to keep them.
  • Before summarisation, labelled passwords, API keys (Anthropic, OpenAI, GitHub, Slack, AWS, Google), JWTs and private keys are replaced with [redacted]. Redaction is pattern-based; an unlabelled secret in free text may not be caught.
  • Condensed transcripts are sent only to Claude, through your own Claude Code login.
  • Unless an account is --private, projects/ is shared, so every account can read every conversation. Use private accounts where clients must be kept separate. Settings, CLAUDE.md, skills and MCP servers are still shared.

Limitations

  • Terms of use. Holding more than one account for your own use is common. Creating accounts to evade usage limits, or sharing or reselling accounts, is not permitted under Anthropic's policies. This tool never switches accounts automatically and is not intended for that purpose.
  • Summaries are lossy. A brief can omit detail; review the first actions of a session started from one.
  • Unofficial. Claude Code's on-disk session format is not a public interface and may change between versions.

Development

bash tests/run.sh                          # 150 tests
bash demo/setup.sh && vhs demo/demo.tape   # re-record the demo GIF (requires vhs)

The test suite is self-contained: a temporary HOME, stand-in claude, codex, gh, tmux, osascript, curl and editor executables, and synthetic session files. It never touches your real configuration or credentials. CI runs it on Ubuntu and macOS.

Issues and pull requests are welcome.

How this differs from other account switchers

Several tools give each Claude Code account its own configuration directory, including an earlier, unrelated project also called claude-account. hatswap starts from the same idea and focuses on moving work between accounts:

  • moving a live conversation to another account (switch), restarting it in the same tab;
  • resuming or forking a conversation on another account, and repository-aware handoff briefs, including to Codex;
  • per-folder accounts (pin), a default account, and a separate Codex login per account;
  • shared settings, skills, rules, MCP servers and history by default, with private history per account when you need it;
  • one setup across the terminal, VS Code/Cursor, Claude Desktop and bots (exec);
  • main runs with CLAUDE_CONFIG_DIR unset. Claude Code names its Keychain item after that variable, so setting it to ~/.claude would look for a login that does not exist, a trap several tools fall into;
  • no automatic rotation between accounts when a limit is hit: usage only reports.

Changelog

See CHANGELOG.md.

License

MIT

agentic-ai
ai-agents
anthropic
claude
claude-code
cli
codex
developer-tools
multi-account
productivity

orvi2014/hatswap

Wear a different hat for each client: run several Claude Code accounts side by side, move a live conversation between them, pin folders to accounts, and hand work to Codex.

Shell

1

25 commits

updated Oct 13, 2026

See the code

See what people are saying

README

hatswap

Every client's Claude Code account, signed in at once. Swap mid-conversation.

test License: MIT Platform: macOS | Linux

hatswap keeps each Claude Code account signed in at the same time and moves work between them — by resuming a conversation, or by starting a fresh session from a compact, repository-aware handoff brief. The same brief can restart an overgrown session on the same account, or seed an OpenAI Codex session.

hatswap: the account picker showing each account's email and 5-hour and weekly usage, setting the default and pinning the folder from it, claude starting on the pinned account inside the folder and on the default outside it, switching a conversation to another account, and handing the task to Codex

Recorded in a sandbox with example accounts and a stand-in for claude; reproduce with bash demo/setup.sh && vhs demo/demo.tape.


Contents


Features

  • Concurrent accounts. Every account stays signed in; run two accounts in two terminals at once.
  • Shared environment. Settings, agents, skills, plugins, hooks, rules, output styles, keybindings, memory and conversation history are shared across accounts. Only the login differs.
  • Switch in place. switch moves the current conversation to another account. In tmux (including desktop apps that host tabs in tmux), Terminal.app and iTerm2, the same tab restarts on the new login with its history intact.
  • Default account. default (or --default) picks the account plain claude starts on; setup-shell installs the one-line hook for zsh, bash or fish.
  • Per-folder accounts. pin acme in a client's folder and every claude started there uses that client's account.
  • Account picker. Type hatswap on its own for a list of every account with its email, limits, default and pin; pick one with the arrow keys and start Claude, set the default or pin the folder with one key.
  • Usage per account. usage shows every account's 5-hour and weekly limits in one table, the numbers /usage shows inside Claude.
  • Account in the status line. statusline --install puts the account and email at the bottom of every session, ahead of any status line you already have.
  • VS Code, Cursor and Claude Desktop. vscode acme makes the Claude extension use acme's login in that folder; desktop acme opens Claude Desktop signed in as acme, in its own window.
  • Bots follow the default. exec (or the claude/codex shims from shim) looks the account up on every call, so a long-running bot that starts claude -p per job moves to a new default without a restart.
  • Codex per account. Each account can have its own Codex login; handoff codex uses the one that belongs to the session's account.
  • Private history. Accounts can keep their conversations and memory to themselves, for client work that must stay separate.
  • One-line install and update. The installer sets up PATH, the shell hook and the slash commands; outdated slash commands are flagged.
  • Session handoff. Continue a conversation on another account with --resume, or fork a copy of it.
  • Brief handoff. Start a fresh session from a structured summary instead of replaying the full history.
  • Refresh. Restart a session that has grown too long, on the same account, from its brief.
  • Repository-aware briefs. Branch, uncommitted changes, recent commits and your open pull requests are collected at handoff time and treated as ground truth.
  • Review before launch. Open the brief in your editor and correct it before the new agent sees it.
  • In-session /handoff and /switch. A running Claude Code session writes its own brief, from full context, at no extra cost, or moves itself to another account.
  • Codex handoff. Seed an OpenAI Codex session with the same brief.
  • Live-session protection. Refuses to resume a conversation that is still open in another window.
  • Secret redaction. Passwords, API keys, tokens and private keys are removed before a transcript is summarised.

Requirements

Operating systemmacOS or Linux
ShellBash 3.2 or later (the macOS default works)
RuntimePython 3
Claude Codeclaude on your PATH
Optionalgh for open pull requests in briefs · codex for handoff codex · tmux for switching a session in place

Setup

1. Install

curl -fsSL https://raw.githubusercontent.com/orvi2014/hatswap/main/install.sh | bash

This installs hatswap to ~/.local/bin, adds that folder to your PATH if needed, installs the default-account shell hook and the /handoff and /switch commands. Open a new terminal afterwards. Every change is marked and reversible (see Uninstall).

Options go after bash -s --, for example curl -fsSL …/install.sh | bash -s -- --no-shell:

OptionEffect
--no-shellDo not edit shell startup files (no PATH line, no default-account hook).
--no-commandsDo not install /handoff and /switch.
--bin-dir <dir>Install somewhere other than ~/.local/bin.

Update later with hatswap update. It also refreshes the slash commands.

Install by hand
mkdir -p ~/.local/bin
curl -fsSL https://raw.githubusercontent.com/orvi2014/hatswap/main/bin/hatswap \
  -o ~/.local/bin/hatswap
chmod +x ~/.local/bin/hatswap

If ~/.local/bin is not on your PATH, add it to your shell profile.

Install from a clone instead
git clone https://github.com/orvi2014/hatswap.git
ln -s "$PWD/hatswap/bin/hatswap" ~/.local/bin/hatswap

git pull in the clone (or hatswap update) then updates the command.

2. Verify

hatswap list

Your existing Claude Code login appears as main. It is used as-is and never modified.

3. Add a second account

hatswap add work

A browser window opens. Sign in with the other account — if the browser is already signed in to your main account, use a private window. Pass --email [email protected] to prefill the address, or --console to use an Anthropic Console (API billing) login.

hatswap list
#   main   [email protected]   (~/.claude)
#   work   [email protected]

4. Let plain claude follow a default account (optional)

hatswap setup-shell          # adds a guarded hook to your shell's startup file
hatswap default work         # plain `claude` now starts on "work"

setup-shell detects zsh, bash or fish (override with --shell), is safe to run twice, and is undone with setup-shell --remove. Open a new terminal afterwards.

5. Enable /handoff inside Claude Code (optional)

hatswap install-command

This adds ~/.claude/commands/handoff.md and switch.md, which every account shares. In any Claude Code session you can then run /switch work, /handoff work, /handoff codex or /handoff.

Upgrading from claude-account

Formerly claude-account. Renamed in 0.5.0 to avoid confusion with an unrelated project of the same name. Old links redirect here, and the claude-account command keeps working as an alias.

claude-account update        # installs hatswap next to it; claude-account becomes an alias

Or re-run the installer. Nothing else changes: your accounts, logins, briefs, default account, pins and shell hook keep working. The data folder is still ~/.claude-accounts, because Claude Code ties each account's Keychain login to that path; moving it would sign every account out. Older names are still honoured:

NewStill accepted
hatswapclaude-account (alias)
.hatswap pin files.claude-account (pin converts it when you re-pin)
HATSWAP_DIRCLAUDE_ACCOUNTS_DIR
HATSWAP_KEEP_AUTH_ENV, HATSWAP_SWITCH_DELAYCLAUDE_ACCOUNT_KEEP_AUTH_ENV, CLAUDE_ACCOUNT_SWITCH_DELAY
# >>> hatswap >>> hook block# >>> claude-account >>>

Run hatswap install-command once to update /handoff and /switch (they now call hatswap); hatswap list reminds you.

Uninstall

hatswap setup-shell --remove # if you added the shell hook
hatswap remove work          # repeat for each added account (also signs out its Codex login)
rm -f ~/.local/bin/hatswap ~/.local/bin/claude-account ~/.claude/commands/handoff.md ~/.claude/commands/switch.md
rm -r ~/.claude-accounts            # saved briefs

The installer's PATH line is marked # hatswap PATH in your shell profile; delete that line too.

Usage

Work on another account

hatswap use work             # or simply: hatswap work
hatswap main                 # back to the main account (~/.claude)

Run each in its own terminal to use both at once. hatswap whoami shows which account the current shell or Claude session is on, and which one a new claude starts on (inside Claude Code, ! hatswap whoami or /status).

Switch the account of the current conversation

Inside Claude Code:

! hatswap switch work            # or /switch work (after install-command)
! hatswap switch work --default  # also make "work" the default for new sessions

A running Claude Code process cannot change its login, so switch restarts it: the same conversation (full history, same ID) resumes on the other account, in the same tab:

Where the session runsWhat happens
tmux, including desktop apps that host each tab in tmuxThe pane restarts in place, keeping its original flags, hooks and MCP config.
Terminal.app or iTerm2 (macOS)Claude exits and the resume command is typed into that tab. macOS asks once to let your terminal control Terminal/iTerm2.
Anything elseType /exit, then run hatswap switch work in that terminal.

Run from a different terminal, switch restarts the session in its own tab when it can, and otherwise closes it there and resumes it in the terminal you ran it from.

Choose the default account

hatswap default work         # plain `claude` now starts on "work"
hatswap use work --default   # set it and start a session
hatswap default main         # back to ~/.claude
hatswap default              # show the current default

The first time you set a default, hatswap offers to add a one-line hook to your shell's startup file (~/.zshenv, ~/.bash_profile / ~/.bashrc, or fish conf.d) so plain claude follows it. For zsh it goes in ~/.zshenv rather than ~/.zshrc so that sessions an app reopens after a restart (with zsh -l -c claude …) also get it; an older hook in ~/.zshrc moves itself there the next time a terminal opens. The hook reads the default on every call, so changing it later needs no reload. An explicit CLAUDE_CONFIG_DIR always wins.

Pin a folder to an account

cd ~/clients/acme && hatswap pin acme   # plain `claude` here, and in every subfolder, starts on "acme"
hatswap pin                             # show which pin applies here
hatswap pin main                        # this folder uses main, whatever the default
hatswap pin --clear                     # remove this folder's pin

pin writes a one-line .hatswap file. Inside a git repository it is added to the repository's local ignore (.git/info/exclude), so it is never committed. The nearest pin wins, so a subfolder can pin a different account. Order of precedence for a plain claude: an explicit CLAUDE_CONFIG_DIR, then the nearest pin, then the default account, then main. whoami shows which one applies and why, and refresh and handoff codex follow it too. Pins work through the same shell hook as the default.

Move a task to another account

hatswap sessions             # conversations in this folder, and where each is open
hatswap handoff work --brief # fresh session on "work", seeded with a brief
hatswap handoff work         # or resume the full conversation (close the original first)

Restart a session that has grown too long

hatswap refresh              # brief + fresh session on the same account
hatswap refresh --edit       # review the brief in $EDITOR first

Hand off from inside a running session

/handoff work

Claude writes the brief from its own context, saves it, and prints the command to continue — for example hatswap handoff work --from ~/.claude-accounts/.briefs/….md.

Continue in Codex

hatswap handoff codex

Give each account its own Codex login

hatswap codex-login acme      # sign "acme" in to its Codex (ChatGPT/OpenAI) account
hatswap codex acme            # start Codex on acme's login
hatswap handoff codex         # uses the session's account's Codex login when it has one
hatswap handoff codex --as acme

Each account's Codex login lives in its own CODEX_HOME (~/.claude-accounts/<name>/codex). config.toml, AGENTS.md, prompts, rules, skills and plugins are shared with ~/.codex; the login, history and sessions are not. main uses ~/.codex. add acme --codex signs in to both at once, and list shows each account's Codex email.

Keep a client's conversations to its own account

hatswap add acme --private    # new account with its own history and memory
hatswap private acme          # or convert an existing one
hatswap private acme --off    # share history again (only while its own history is empty)

By default every account shares conversation history. A private account keeps its conversations and memory in its own projects/ folder, so other accounts never read them; settings, skills and MCP servers are still shared. sessions, brief and handoff see private conversations too. When you deliberately hand one to another account, it is copied into that account's history and the command says so.

Pick an account from a list

Type hatswap with nothing after it in a terminal:

hatswap ─ new sessions in ~/clients/acme start on acme

   main     [email protected]    5h    0% ░░░░░░  week  100% ██████
 ▸ acme     [email protected]        5h   10% █░░░░░  week   27% ██░░░░  ★ default  codex

 ↑↓ choose   ⏎ start claude   d default   p pin folder   s continue last chat   c codex   l sign in   q quit

It draws in place rather than taking over the screen, so it behaves in tmux and terminal apps. Scripts, pipes and ! inside Claude still get the help text; HATSWAP_NO_PICKER=1 turns it off.

See every account's limits

hatswap usage            # all accounts
hatswap usage acme       # one; --json for scripts
ACCOUNT        EMAIL                          5-HOUR  RESETS      WEEK  RESETS
main           [email protected]                   0%              100%  Fri 14:00
acme           [email protected]                       5%  in 0h44m     23%  Sat 16:00

The numbers come from the same endpoint Claude's /usage reads. It is not a public API, so it may change; results are cached for 3 minutes because it is rate-limited. hatswap reads each login from the Keychain (or .credentials.json on Linux) only to make that request: tokens are never printed, written or refreshed. An expired one says so; starting Claude on that account refreshes it. hatswap never switches accounts for you.

Show the account in Claude's status line

hatswap statusline --install   # ⎈ acme · [email protected]  │ <your existing status line>
hatswap statusline --remove    # put back what you had

All accounts share settings.json, so one install covers every account. A status line you already had keeps running, after the account.

Use an account in VS Code, Cursor or Claude Desktop

cd ~/clients/acme
hatswap vscode acme        # the Claude extension uses acme in this folder
hatswap vscode             # same, using this folder's pin or the default
hatswap vscode --clear
hatswap desktop acme       # Claude Desktop as acme, in its own window

vscode sets CLAUDE_CONFIG_DIR in the folder's .vscode/settings.json (claudeCode.environmentVariables), keeping your other settings, and adds the file to the repository's local ignore. Reload the window afterwards and open that folder itself, not a parent. For main it removes the entry, because Claude finds main's login only when the variable is unset. A settings.json with comments is left alone and the line to add is printed. The extension's own session list may still show ~/.claude conversations (anthropics/claude-code#30538).

desktop starts a separate Claude Desktop with its own profile folder (~/.claude-accounts/<name>/desktop), so each account has its own window and login. Sign in once; it stays signed in. desktop main opens the app as usual.

Run bots and scripts on the current default

The shell hook only covers claude typed in a terminal. A bot started by launchd, cron or pm2 runs the claude binary directly, so it stays on whatever account it started with. Route it through hatswap instead:

hatswap exec claude -p "summarise today's issues"   # runs on the pin, then the default, at call time
hatswap shim                                        # writes ~/.claude-accounts/bin/claude and codex

Point the bot at ~/.claude-accounts/bin/claude (or put ~/.claude-accounts/bin first on its PATH). Each call looks up the account again, so hatswap default acme applies to the bot's next call, with no restart. The lookup uses the bot's working folder, so a pinned folder still wins. To keep one bot on one account whatever the default is, set HATSWAP_ACCOUNT=acme in its environment (or use exec --account acme).

exec ignores a CLAUDE_CONFIG_DIR the bot inherited, because following it would keep the bot on its old account. It clears ANTHROPIC_API_KEY and the other login variables for named accounts, quietly, and sets CODEX_HOME when the account has its own Codex login. A process that is already running keeps the account it started on: a bot that holds one Claude session open for hours picks up the new default only when it next starts claude. Keep the shim folder off your interactive PATH; the shell hook is the right tool there.

Work with saved briefs

hatswap briefs                          # list, with each brief's goal
hatswap handoff work --from <file>      # start any account from a saved brief
hatswap brief --edit                    # write, review and print a brief without switching

Command reference

CommandDescription
add <name> [--email <addr>] [--console] [--private] [--codex]Create an account profile and sign in. --private keeps its history to itself; --codex also signs it in to Codex.
private <name> [--off]Give an existing account its own history and memory, or share again.
codex-login <name|main>Sign an account in to its own Codex login.
codex <name|main> [codex args…]Start Codex on that account's Codex login.
use <name> [--default] [claude args…]Start Claude Code on an account. hatswap <name> is equivalent. --default also makes it the default.
(no command)In a terminal: the account picker. Otherwise the help text.
exec [--account <name|main>] <command> [args…]Run a command (claude, codex, a bot) on the account a new session would get now: --account, then HATSWAP_ACCOUNT, then the folder pin, then the default. No terminal needed.
usage [name…] [--json]Each account's 5-hour and weekly limits and reset times. Read-only.
statusline [--install|--remove]Show the account in Claude Code's status line, keeping an existing one.
vscode [name|main|--clear]Make the Claude extension in VS Code or Cursor use that account in this folder.
desktop [name|main]Open Claude Desktop signed in to that account, in its own window (macOS).
shim [--remove]Write (or remove) claude and codex wrappers in ~/.claude-accounts/bin that run through exec.
main [claude args…]Start Claude Code on the main account (~/.claude).
switch <target> [id] [--default] [--yes]Continue a conversation on another account, restarting it in its own tab (tmux, Terminal.app, iTerm2) or resuming it here.
default [name|main]Show or set the account plain claude starts on.
pin [name|main|--clear]Pin the current folder and its subfolders to an account, show the pin, or remove it.
setup-shell [--remove] [--shell zsh|bash|fish]Install or remove the hook that makes plain claude follow the default.
shell-init [zsh|bash|fish]Print that hook.
listList accounts and the email each is signed in as.
whoamiShow the account this shell or session is on, and the one new sessions start on.
--versionPrint the version.
login <name>Sign an account in again.
remove <name>Sign out and delete a profile; shared files are kept.
sessions [n]Recent conversations in the current folder and where each is open.
handoff <target> [id] [--fork]Resume a conversation on another account.
handoff <target|codex> [id] --brief [--as <name>]Start a fresh session there from a brief. For Codex, --as picks whose Codex login to use.
refresh [id]Brief and fresh session on the same account.
brief [id]Write and print a brief without switching.
briefs [n]List saved briefs.
install-command [--force]Install or update the /handoff and /switch commands. --force also replaces files it did not write.
updateUpdate hatswap (a clone is updated with git pull) and its slash commands.

<target> is an account name or main. Without an id, the most recent conversation in the current folder is used; IDs are listed by sessions.

Brief options (for handoff --brief, refresh and brief):

OptionEffect
--editOpen the brief in $VISUAL / $EDITOR before it is used.
--from <file>Use a saved brief instead of writing a new one.
--model <name>Model that writes the brief (default sonnet).
--no-repoLeave out repository state.

Commands that start Claude Code need a terminal; they will not run through Claude Code's ! prefix. switch is the exception.

When the installed /handoff or /switch is older than the current templates, list, whoami and the handoff commands print a one-line note to run hatswap install-command.

Handoff modes

--resume (default)--fork--brief / refresh
New session starts withthe full conversationa copy of the full conversationa structured brief
Context loadedentire historyentire historytypically 1–3k tokens
Original sessionmust be closedmay stay openmay stay open
TargetsClaude CodeClaude CodeClaude Code or Codex
Fidelityexactexactsummarised, then verified against the repository

Use --resume when exact continuity matters. Use a brief for long sessions, when the original is still running, or when moving to Codex. In testing, a 17 MB compacted session produced a brief of about 7 KB.

The handoff brief

A brief is Markdown with fixed sections:

SectionContents
GoalWhat the user asked for, in their words where possible
Done (verified)Completed work with evidence: pull requests, commits, deploys, test results
In flightThe step in progress when the session stopped
Decisions and constraintsChoices and rules the user set, quoted when strict
Tried and rejectedApproaches that were tried or considered and dropped, with the reason, so they are not repeated
Key pointersRepositories, branches, files, commands, URLs and IDs
Open questions / blockersAnything blocked or waiting on the user
Next stepOne or two concrete first actions

The receiving agent is told to verify the repository before relying on the brief, and to confirm with the user before any destructive or outward-facing action.

Two ways to write one

hatswap brief / --brief/handoff inside Claude Code
Written bya separate claude -p callthe running session itself
Inputcondensed transcript + repository statefull live context + repository state
Billed tothe account receiving the handoffthe current account
Works after the session has closedyesno

How the transcript is condensed. The input reflects what the model actually had in context: the latest compaction summary, the messages Claude Code retained verbatim when it compacted, and everything after. It keeps user messages, assistant replies, one-line tool calls and tool errors, and drops side chains, system reminders and tool output. Repository state is appended as a block the model treats as ground truth. The claude -p call uses a dedicated system prompt with tools, MCP servers and project settings disabled.

Briefs are saved to ~/.claude-accounts/.briefs/ with mode 600.

How it works

Hand-drawn diagram: a shared ~/.claude is symlinked into the main and work accounts, each with its own login; handoff --brief condenses an old session into a ~7 KB brief that seeds a fresh session on another account, the same account (refresh), or Codex

  • Isolation. Each account is a separate CLAUDE_CONFIG_DIR under ~/.claude-accounts/<name>. Claude Code stores one login per configuration directory — in the macOS Keychain, or in the directory itself on Linux — so accounts never overwrite each other. The default account runs with CLAUDE_CONFIG_DIR unset.
  • Sharing. settings.json, settings.local.json, CLAUDE.md, keybindings.json, agents/, skills/, plugins/, commands/, hooks/, rules/, output-styles/ and projects/ are symlinked from ~/.claude. User-scope MCP servers are copied once when an account is added.
  • Session discovery. Conversations are read from the shared projects/ directory. Open sessions are detected from each account's session registry and confirmed by process ID.
  • Switching. A running Claude Code process cannot change its login, so switch restarts it with --resume under the other account. If the session's registry entry names a tmux pane, the restart is scheduled through the tmux server (respawn-pane), so it works from inside the session and keeps the pane's original start command. Otherwise the session is asked to exit (SIGTERM) and resumed in the terminal that ran switch.
  • Private history. A private account has a real projects/ folder instead of the shared symlink, and a .private marker so it is never re-linked. Session discovery scans every distinct history folder.
  • Codex. Codex reads its login from $CODEX_HOME; each account gets ~/.claude-accounts/<name>/codex, with configuration symlinked from ~/.codex. The Codex email shown by list is read from the login's ID-token claims; tokens are never printed or copied.
  • Default account and pins. The default is a name in ~/.claude-accounts/.default; a pin is a .hatswap file holding an account name. The shell hook defines a claude function that, on every call, looks for the nearest .hatswap from the current folder upwards, falls back to the default, and sets CLAUDE_CONFIG_DIR only when it is not already set. It uses shell builtins for the folder walk, so it adds no noticeable start-up time.

Security and privacy

  • No credentials are read, copied or modified; sign-in is handled entirely by Claude Code.
  • ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN and CLAUDE_CODE_OAUTH_TOKEN override a login, so they are cleared (with a one-line note) whenever Claude starts on a named account, including through the shell hook. Your shell keeps them, and main is left alone. Set HATSWAP_KEEP_AUTH_ENV=1 to keep them.
  • Before summarisation, labelled passwords, API keys (Anthropic, OpenAI, GitHub, Slack, AWS, Google), JWTs and private keys are replaced with [redacted]. Redaction is pattern-based; an unlabelled secret in free text may not be caught.
  • Condensed transcripts are sent only to Claude, through your own Claude Code login.
  • Unless an account is --private, projects/ is shared, so every account can read every conversation. Use private accounts where clients must be kept separate. Settings, CLAUDE.md, skills and MCP servers are still shared.

Limitations

  • Terms of use. Holding more than one account for your own use is common. Creating accounts to evade usage limits, or sharing or reselling accounts, is not permitted under Anthropic's policies. This tool never switches accounts automatically and is not intended for that purpose.
  • Summaries are lossy. A brief can omit detail; review the first actions of a session started from one.
  • Unofficial. Claude Code's on-disk session format is not a public interface and may change between versions.

Development

bash tests/run.sh                          # 150 tests
bash demo/setup.sh && vhs demo/demo.tape   # re-record the demo GIF (requires vhs)

The test suite is self-contained: a temporary HOME, stand-in claude, codex, gh, tmux, osascript, curl and editor executables, and synthetic session files. It never touches your real configuration or credentials. CI runs it on Ubuntu and macOS.

Issues and pull requests are welcome.

How this differs from other account switchers

Several tools give each Claude Code account its own configuration directory, including an earlier, unrelated project also called claude-account. hatswap starts from the same idea and focuses on moving work between accounts:

  • moving a live conversation to another account (switch), restarting it in the same tab;
  • resuming or forking a conversation on another account, and repository-aware handoff briefs, including to Codex;
  • per-folder accounts (pin), a default account, and a separate Codex login per account;
  • shared settings, skills, rules, MCP servers and history by default, with private history per account when you need it;
  • one setup across the terminal, VS Code/Cursor, Claude Desktop and bots (exec);
  • main runs with CLAUDE_CONFIG_DIR unset. Claude Code names its Keychain item after that variable, so setting it to ~/.claude would look for a login that does not exist, a trap several tools fall into;
  • no automatic rotation between accounts when a limit is hit: usage only reports.

Changelog

See CHANGELOG.md.

License

MIT

agentic-ai
ai-agents
anthropic
claude
claude-code
cli
codex
developer-tools
multi-account
productivity