Running Multiple Claude Desktop Instances Side by Side

TL;DR: Claude Desktop doesn’t support multiple accounts. I tried symlink swapping (breaks VirtioFS) and directory swapping (race conditions with running VMs), before finding the clean solution: Electron’s --user-data-dir flag. One command – open -n -a "Claude.app" --args --user-data-dir="~/.claude-instances/work" – gives you a fully isolated instance with its own auth, MCP servers, and Cowork environment.

I have two Claude accounts. A personal one with its own extensions – integration to my Obsidian vault, PhpStorm proxy, some skills, and scheduled Cowork agents – and a company account on a separate subscription with its own set of extensions. Claude Desktop only supports one login at a time. Switching means logging out – which disables the Cowork schedule – and logging into the other account to access its extensions.

My workaround was running my personal account in the desktop app, and the company account in my browser (I use Arc, btw). It works for chat. But a browser session can’t connect to local MCP servers – no PhpStorm context, no access to Code or Cowork from the Claude UI.

I wanted to find out whether there’s a clean way to run multiple isolated instances from a single install. There is – but the first two approaches I tried broke in ways that reveal how Claude Desktop actually works under the hood.

I started with a community script that took a straightforward approach. Claude Desktop stores its config at ~/Library/Application Support/Claude. The idea: create multiple config directories, symlink that path to the one you want, launch Claude.

ln -sf ~/.claude-instances/work ~/Library/Application\ Support/Claude
open -n /Applications/Claude.app

This works for config and basic chat. Claude reads the right claude_desktop_config.json from the symlink target. You can even accept the limitation that config writes – installing MCP servers, adding skills – only go to whichever instance is currently symlinked. For a quick prototype, that’s fine.

The problem is that Claude Desktop stores far more than config in that directory. VM bundles, the Claude Code SDK, and session disk images for the Cowork environment all live there. Cowork is Claude’s built-in sandboxed Linux VM – it runs background tasks, executes code, and mounts your filesystem via VirtioFS.

Gotcha: Symlinks break VirtioFS path resolution. The VM sees the resolved target path, not the symlink – and the target directory only has your config JSON, not the SDK or disk images.

When the VM boots, it resolves the symlink on the host side and looks for the SDK at the real filesystem path. The symlink target only contains the config. The result:

SDK version X.Y.Z not verified at
/mnt/.virtiofs-root/shared/Library/Application Support/Claude/claude-code-vm/X.Y.Z/.verified

The macOS Virtualization framework also threw a VZErrorDomain Code=2 error about invalid storage device attachments – the VM config referenced disk images that weren’t at the expected path.

Chat works. Everything else breaks. Not what I wanted.

Directory Swapping – More Problems Than Fixes

The fix for this symlink issue seemed obvious: swap real directories instead. mv the actual Application Support/Claude directory to the instance slot, mv the target instance’s directory into place. Real files at real paths. VirtioFS resolves correctly. The VM boots.

But this introduced worse problems.

Gotcha: You can’t mv a directory while a running VM holds file locks on disk images inside it. And even if you could, you’d be pulling the data directory out from under a live app.

Launching instance B while instance A is running would move A’s data directory out from under a live Electron process with an active VM. That’s not a stale-config problem – it’s a pull-the-rug-out problem. The skill-registration limitation persists too: MCP server connections and skill installs still go to whichever directory occupies the canonical path.

The complexity spiraled. I needed .active marker files to track which instance was in place, pgrep -xq "Claude" checks to prevent swaps while Claude was running, and a restore command to put things back on exit. All of this for what’s fundamentally a sequential tool. Quit Claude, swap, relaunch. That’s not side-by-side operation.

I didn’t spend much time on this version but kept digging.

Electron’s --user-data-dir Is the Entire Solution

Claude Desktop is an Electron app. Electron apps accept a --user-data-dir flag that redirects all application data to a custom directory. No swapping, no symlinks, no state tracking.

open -n -a "/Applications/Claude.app" --args --user-data-dir="$HOME/.claude-instances/work"

That’s it. Each instance gets its own completely isolated sandbox: config, VM bundles, SDK, session data, auth tokens – everything lives in its own directory. Two instances run side by side with full capabilities. MCP servers work. Cowork works. The “view logs” and “edit config” buttons in app settings open the correct location for each instance.

Insight: The entire launch function is three lines. Everything else – wrappers, listing, deletion, diagnostics – is scaffolding around --user-data-dir.

The core of the launcher script:

launch_instance() {
    local instance_name="$1"
    local instance_dir="$INSTANCES_BASE/$instance_name"
    mkdir -p "$instance_dir"
    open -n -a "/Applications/Claude.app" --args --user-data-dir="$instance_dir"
}

Minor tradeoffs: Each instance bootstraps its own VM environment on first launch – roughly 1-2 GB of disk, but it happens automatically. First launch is slower while Electron sets up the data directory from scratch. And claude:// deep links – used for SSO login – route to whichever Claude instance registered the URL scheme last. The fix is simple: log in to each instance one at a time with the others closed. After that, both sessions persist independently.

Wrapping Instances for the Dock

A shell command works. But I wanted each instance to show up as a real macOS app – in Launchpad, in the Dock, in Cmd+Tab. macOS .app bundles make this possible with minimal scaffolding.

Each wrapper contains two files. config.sh holds the instance identity:

INSTANCE_NAME=work

claude-launcher is a static three-line script that delegates to the main launcher:

#!/bin/bash
source "$(dirname "$0")/config.sh"
exec /usr/local/bin/launch-claude "$INSTANCE_NAME"

Insight: The .app filename is cosmetic. Instance identity lives in config.sh, not the bundle name – rename “Claude Work.app” to anything, and it still launches the right instance.

This separation is deliberate. The fix command can regenerate all launcher scripts – say, if the main script moves to a new path – without touching the instance binding. Wrapper discovery scans /Applications/*.app for bundles containing a config.sh with an INSTANCE_NAME= line, rather than relying on filename conventions.

A special virtual instance called “default” launches Claude without --user-data-dir, preserving standard behavior. It uses the app’s built-in data directory – the same one you get when launching Claude normally from the Dock. “default” can’t be created or deleted, but it can be wrapped and listed like any other instance.

Full Isolation From a Single Flag

Electron’s --user-data-dir flag gives you fully isolated Claude Desktop instances from a single installed app. No copying, no renaming, no fragile directory swaps. Each instance gets its own auth, its own MCP servers, its own Cowork environment.

The starting point is one command:

open -n -a "/Applications/Claude.app" --args --user-data-dir="~/.claude-instances/work"

The launcher script adds convenience – Dock wrappers, instance management, and diagnostics – but the core is that one flag. The full script is on GitHub.

This is macOS-specific. The open -n -a syntax is a macOS convention. On Linux or Windows, you’d invoke the Electron binary directly with the same --user-data-dir flag. I haven’t tested that.