I work with several companies, and nearly everything I do for them runs through Claude in one form or another. I want a separate Claude account for each one. Separate billing, separate organization, separate project history, no chance of a file from one client's work turning up in a session for another. That is the whole requirement, and it is not an unusual one.
Claude does not do it. Claude Code authenticates one account per OS user. The desktop app holds one login at a time. Multi-account switching is one of the most requested features for both surfaces and it has not shipped for either. The phone app has it, which is its own small insult when you are sitting in front of the machine where you actually do the work.
So I built it myself, on a Saturday, and it mostly works. Here is the setup, the traps that cost me time, and the one part that is genuinely impossible.
The thing that makes it possible
Both surfaces already key their stored credentials to a directory. Point them at different directories and you get different logins. No scripting around credential files, no logging out and back in, no token juggling.
The catch is that the CLI and the desktop app use completely different directory mechanisms, and neither one reads the other's. I spent a while trying to make the desktop app respect the CLI's environment variable before accepting that it never reads it. It reads no authentication environment variables at all. Not CLAUDE_CONFIG_DIR, not ANTHROPIC_API_KEY, not an API key helper. It authenticates through an OAuth browser session living in its profile directory, and that is the only route in.
For Claude Code, the switch is CLAUDE_CONFIG_DIR, an environment variable set per shell. The directory it points at holds settings, projects, MCP servers, agents, skills and command history, plus the login. Default is ~/.claude.
For the desktop app, the switch is --user-data-dir, an Electron launch flag. The directory it points at is a full Chromium browser profile: cookies, cache, local storage, and the app's own MCP config. Default is ~/Library/Application Support/Claude.
They have nothing in common beyond both being folders.
The Keychain detail that makes the CLI version actually work
This is the part I nearly got wrong, and getting it wrong would have meant abandoning the whole idea.
Look in Keychain Access and you find a single item named Claude Code-credentials. The obvious conclusion is that every config directory shares one credential, so separate directories can only give you separate settings with one account sitting behind all of them. Useful, but not what I wanted.
That conclusion is wrong. The Keychain service name is derived from the config directory's absolute path. The default ~/.claude gets the bare unsuffixed name, and every other directory gets its own entry with the first eight hex characters of the path's SHA-256 appended. You can compute the name for any directory and confirm the item is there:
d="$HOME/.claude_clientwork"
printf '%s' "$d" | shasum -a 256 | cut -c1-8
# on my machine: 48c315e0
security find-generic-password -s "Claude Code-credentials-48c315e0"
# exists, independent of every other entryThe documentation says this plainly once you know what you are looking for: Claude Code keys the macOS Keychain entry to the config directory, so a session with a different CLAUDE_CONFIG_DIR reads a different entry. On Linux and Windows there is no Keychain and the credential sits in a .credentials.json inside the directory itself, which amounts to the same thing.
What this buys you is a genuinely different email address per directory, not just a different organization on one account. Switching a directory over is /logout then /login inside a session running under it. Nothing else on the machine is touched.
Everything else in the directory is scoped the same way, which is mostly what you want and occasionally a nuisance. When I went looking for where a day's tokens had actually gone, my first measurement came back listing three projects I did not recognize, because the tool had read the default directory while the work I was chasing lived under a different one.
The trap is in the path itself. Because the Keychain name is a hash of that exact string, two spellings of the same directory are two different credentials. A relative path, a trailing slash, or a symlinked path hashes to an entry that does not exist. The session does not error. It just finds no credential and asks you to log in again, which reads exactly like a session expiring rather than like a typo. Resolve the path to something absolute and symlink-free before you export it.
The part that answers the terminal window problem
The variable is read per process, so one exported value covers everything a script launches afterward. That is the useful shape: one small script per account that sets the variable once and then opens the windows for that account's projects.
#!/bin/bash
export CLAUDE_CONFIG_DIR="$HOME/.claude_clientwork"
cd ~/Code/ClientWork
open-session ./api --name "API"
open-session ./storefront --name "Storefront"
open-session ./admin --name "Admin"open-session is a thin wrapper that opens a terminal window, exports the variable and starts the CLI. Two details make it pleasant rather than fragile: it inherits an already-set CLAUDE_CONFIG_DIR, so the account is stated once at the top of the script, and it accepts a per-call override, so a single window can be pointed somewhere else.
The result is that I stopped hand-assembling a workspace every morning. One command per client, and that client's windows come up under that client's account.
Desktop profiles and the Dock
The desktop app is Electron, so it inherits Chromium's profile flag. One command gives a fully isolated second instance:
open -n -a /Applications/Claude.app --args --user-data-dir="$HOME/.claude-desktop/clientwork"The -n forces a new instance instead of focusing the running one. On first launch the directory fills with a complete Chromium profile, around thirty entries, while the default profile is untouched. Both instances then run side by side with independent logins, independent MCP servers and independent settings.
A launch flag is not clickable, so the packaging is a small .app bundle per profile whose only executable is a shell script calling the launcher. Each bundle carries its own icon, which is what makes the Dock readable. Generating the icons is cheap: unpack the real app's .icns, rotate the hue per profile, repack. About a megabyte per bundle.
ClaudeWork.app/
Contents/
Info.plist # unique CFBundleIdentifier, LSUIElement, icon name
MacOS/launch # exec claude-desktop clientwork
Resources/icon.icns # hue-rotated from the real app iconSet LSUIElement so the wrapper does not sit in the Dock as a running app. It fires and exits in a fraction of a second. Pin the bundles and each profile is one click.
Four things that bit
The sign-in lands in the wrong window. This one made the whole setup look broken. Sign-in uses a claude:// deep link, and macOS routes that to whichever instance is running, not to the one that opened the browser. Authenticate from a new profile while your main window is open and the session gets written to the main profile while the new one stays logged out. It presents as "I log in and it throws me back to the original app." Quit every other instance, sign in, done. Once per profile. The way to confirm which profile actually received the session is to compare the sizes of the Cookies files: an authenticated profile is visibly bigger, 44 KB against 20 KB in my case.
Never point the desktop profile flag at a CLI config directory. They are both "the Claude directory" so the temptation to reuse one is real. Mac volumes are case-insensitive by default. The CLI keeps a cache/ directory and Chromium wants Cache/, which on APFS is the same path, and Chromium runs size-based eviction on its cache. It will quietly delete whatever the CLI left there. I reproduced that one deliberately to be sure. Beyond the collision, merging the two means a routine "reset my desktop profile" also destroys your agents, skills, commands and project history. My launcher now refuses any directory containing a .claude.json.
Two processes on one Chromium profile corrupts it. Profiles are single-writer, and open -n explicitly bypasses the single-instance check, so it will happily start a second process against a directory that already has one. Track running instances by their data directory and never launch into an occupied one. For the default profile, use plain open so it focuses rather than duplicating.
Every profile builds its own VM. If you use the sandboxed environment feature, each profile bootstraps a separate Linux VM bundle. The one I measured carried an 8.3 GB VM inside 11 GB total. The community write-ups quoting one or two gigabytes per profile are describing profiles that never used it. Worth knowing before you create a fourth and a fifth.
The one thing I couldn't fix
Pinned launchers can carry any icon you like. Running app icons cannot. macOS takes a running app's Dock icon from its bundle, and every instance shares one bundle, so three open profiles give you three identical icons.
The obvious answer is three separate copies of the app. I tried it, because the first two objections turn out not to be objections at all.
Copying an 859 MB app three times sounds expensive and isn't. APFS copy-on-write clones finish in half a second with no measurable disk use, because the 859 MB is apparent size and the blocks are shared. Re-badging sounds like it would break the code signature, and it does, but recoverably: sign inside-out, nested frameworks and helpers first and the outer bundle last, and the result passes codesign --verify --strict cleanly.
Then it dies on launch. SIGKILL, exit 137, spawn failed.
The cause is the entitlement set. The app carries entitlements bound to the vendor's Apple team identifier, alongside an embedded provisioning profile, including the virtualization entitlement and the WebAuthn and hardware-key Keychain groups. Apple's runtime integrity enforcement will not honor team-bound entitlements under a signature that is not from that team, and an ad-hoc signature never can be. You can strip the offending entitlements and the process will start, at the cost of the sandboxed environment feature and passkey sign-in. That is trading two real features for an icon, so no.
The realistic path to per-instance icons is Anthropic shipping profile support, because an Electron app can set its own Dock icon at runtime for free once it knows which profile it is. From outside, it is closed.
The workarounds are ordinary. Apps appear in the Dock in launch order, so a consistent launch sequence makes position the identifier. And the app shows the signed-in account in its own interface, so the windows are distinguishable once they are open. You can also pin a running instance, but understand what you are pinning: because every instance shares one bundle, pinning three of them writes three identical tiles all pointing at the same application. While the instances are running the Dock associates each tile with a window and it behaves. After a restart the association is gone and all three are just the stock app, launching the default profile. Stable within a session, meaningless across one, and it doubles your Claude icons to six.
What I actually got
The headline is multiple accounts, but the useful result showed up before the second account existed.
Three desktop profiles signed into the same email still each remember their own organization selection, independently. Three windows stay pinned to three contexts and the organization switcher stops being part of the daily loop. That property belongs to the profile rather than the account, so it survives the later move to genuinely separate logins, and it means the setup paid for itself immediately instead of only once I was paying for a second subscription.
On the CLI side it is one config directory per client. Projects, MCP servers, agents, skills and history are all scoped to it on top of the credential. On the desktop side it is one profile per context, each with its own organization, servers and settings. Both are ordinary directories, so archiving a client means archiving a folder and retiring one means deleting it.
The two halves stay independent, which turns out to be a feature. A CLI session can bill one account while a desktop window on the same machine holds another, because neither mechanism knows the other exists. The phone was never part of the problem, since it already switches accounts on its own.
Five of six goals met. The two that failed are the same limitation from two angles: a running instance is not distinguishable from its siblings, because as far as the operating system is concerned it is the same application. Everything that carries actual state, the login, the organization, the servers, the history, separates cleanly.
All of this was measured on one machine on 14 September 2026, against Claude Code 2.1.270 and desktop 1.52386.6 on macOS 15. It will drift.