{"id":3942,"date":"2026-10-06T10:00:00","date_gmt":"2026-10-06T07:00:00","guid":{"rendered":"https:\/\/eminbudak.com\/blog\/?p=3942"},"modified":"2026-10-04T20:12:25","modified_gmt":"2026-10-04T17:12:25","slug":"claude-multi-account","status":"publish","type":"post","link":"https:\/\/eminbudak.com\/blog\/claude-multi-account\/","title":{"rendered":"Claude Multi-Account: How I Keep Desktop Profiles Aligned"},"content":{"rendered":"<p>Multiple Claude accounts on one Mac were awkward enough that I built <a href=\"https:\/\/github.com\/EminBudak\/claude-multi-account\" target=\"_blank\" rel=\"nofollow noopener\">claude-multi-account<\/a>. Claude Desktop keeps one signed-in account per app. Switching accounts clears the sign-in, and the Claude Code sidebar can look empty even though the transcripts are still there. The tool runs several signed-in Claude Desktop profiles and gives each one the same Claude Code session list.<\/p>\n<p>It&#8217;s a small macOS tool written in Bash and Python 3 using the standard library, MIT licensed, unofficial, and not affiliated with Anthropic. Rather than altering Claude.app, it starts the unmodified app with a separate Electron user-data directory for each profile, then reconciles the small index files behind the session list.<\/p>\n<h2 id=\"the-problem\">Why one desktop app is awkward for several accounts<\/h2>\n<p>Claude Desktop is organized around one signed-in account at a time. There is no account picker or profile switcher. Switching accounts isn&#8217;t a light UI action either. The OAuth cache is cleared, so the next account needs a browser sign-in again.<\/p>\n<p>The stranger part is the sidebar. Claude Code transcripts live under <code>~\/.claude\/projects\/<\/code>, which all accounts on the Mac share. The desktop app&#8217;s session records are different: each account has its own store, keyed by account and organization. Change accounts and the new sidebar may not know about the transcripts already on disk. The work didn&#8217;t disappear. Its index did.<\/p>\n<p>I didn&#8217;t want separate copies of Claude.app or a modified bundle. Both are bad maintenance stories. <a href=\"https:\/\/github.com\/EminBudak\/claude-multi-account\/blob\/main\/README.md\" target=\"_blank\" rel=\"nofollow noopener\">The repository README<\/a> describes the split that matters: leave shared Claude Code data alone and give each desktop login its own application state.<\/p>\n<h2 id=\"profile-model\">Running multiple Claude accounts with separate profiles<\/h2>\n<p>The launcher starts one unmodified <code>Claude.app<\/code> instance with Electron&#8217;s <code>--user-data-dir<\/code> option. A profile gets its own login, cookies, app settings, and session-record store under <code>~\/.claude-profiles\/&lt;email&gt;<\/code>. Several profiles can be open at once, each in its own window.<\/p>\n<p>That distinction matters. The profiles isolate the desktop app state, while <code>~\/.claude<\/code> stays shared because it wasn&#8217;t account-scoped to begin with. I&#8217;m not copying transcripts between accounts. The tool doesn&#8217;t read or write <code>~\/.claude\/projects\/<\/code>.<\/p>\n<p>The Bash launcher also handles a macOS detail that surprised me while building it. A new profile must be the only running Claude instance during its first sign-in, because the <code>claude:\/\/<\/code> callback is delivered to the instance that registered first. Once a profile is signed in, the profiles can coexist. The implementation and its checks are in <a href=\"https:\/\/github.com\/EminBudak\/claude-multi-account\/blob\/main\/bin\/claude-multi\" target=\"_blank\" rel=\"nofollow noopener\">bin\/claude-multi<\/a>.<\/p>\n<h2 id=\"sync\">Session-list synchronization stays merge-only by default<\/h2>\n<p>The sync engine deals only with small session record files. For each session ID, it selects the copy with the newest <code>lastActivityAt<\/code> value and writes that record to every store that is missing it or has an older copy. It&#8217;s a two-way merge, not a preferred-account mirror.<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/eminbudak.com\/blog\/wp-content\/uploads\/2026\/10\/claude-multi-account-sync.webp\" alt=\"claude-multi-account merge-only session synchronization\" \/><figcaption>Session records converge on the newest valid copy, then flow to profile stores that need an update.<\/figcaption><\/figure>\n<p>I made the default deliberately boring. <code>claude-sessions-sync<\/code> starts as a dry run. <code>--apply<\/code> performs the merge, but it still can&#8217;t remove a record or deletion marker. Deletions need the separate <code>--propagate-deletions<\/code> flag. The code also treats unreadable input as a reason to skip that item, not a reason to guess.<\/p>\n<p>That behavior is deliberate. The Python implementation moves only the session indexes, validates the records it uses, and uses the newest valid timestamp as the tie-breaker. Its source is <a href=\"https:\/\/github.com\/EminBudak\/claude-multi-account\/blob\/main\/bin\/claude-sessions-sync\" target=\"_blank\" rel=\"nofollow noopener\">bin\/claude-sessions-sync<\/a>. A synced session list becomes visible after a restart or an account switch because the desktop app reads that list at those points rather than watching the directory live.<\/p>\n<h2 id=\"quick-start\">Install and set up claude-multi-account from a terminal<\/h2>\n<p>The installer links two commands rather than copying them. It uses <code>\/usr\/local\/bin<\/code> when that directory is writable, otherwise <code>~\/.local\/bin<\/code>. It checks for a working Python 3, doesn&#8217;t use <code>sudo<\/code>, doesn&#8217;t launch Claude, and doesn&#8217;t install the background job by itself.<\/p>\n<p>The quick start from the README is:<\/p>\n<pre><code>git clone https:\/\/github.com\/EminBudak\/claude-multi-account.git\ncd claude-multi-account\n.\/install.sh\n\nclaude-multi doctor\nclaude-multi setup work@example.com personal@example.com<\/code><\/pre>\n<p>Run <code>doctor<\/code> before creating anything. Run <code>setup<\/code> from a normal Terminal.app shell, not from Claude Code inside Claude Desktop. Setup has to close every Claude Desktop window before it begins and again between accounts. It asks for confirmation because a clean first sign-in needs one running instance.<\/p>\n<p>After install, I&#8217;d still start with the dry run: <code>claude-sessions-sync<\/code>. Read the plan. Use <code>claude-sessions-sync --apply<\/code> only when it matches what you expect. The background job is optional and can run on an interval, at load, and when an account switch changes the relevant configuration.<\/p>\n<h2 id=\"agents\">What an AI coding agent can and cannot do<\/h2>\n<p>An AI coding agent can clone the repository, run the installer, run <code>doctor<\/code>, inspect the sync dry run, apply a merge-only sync, inspect status, and create a profile launcher when the sign-in conditions are safe. It can also help interpret the local checks.<\/p>\n<p>Two jobs stay with a person. The browser sign-in uses real credentials and comes back through the system URL handler, so it needs a human once for each profile. <code>claude-multi setup<\/code> also stays with a person in Terminal.app. It intentionally quits Claude Desktop, needs a real terminal, and refuses to start inside a Claude Code session unless overridden. An agent shouldn&#8217;t reach for that override.<\/p>\n<p>There&#8217;s one small command-line trap worth knowing. Options belong after the subcommand: <code>claude-multi work@example.com -y<\/code> is accepted, while <code>claude-multi -y work@example.com<\/code> isn&#8217;t. The <code>-y<\/code> flag can answer a profile-creation confirmation, but it can&#8217;t perform the browser sign-in or dismiss every sign-in warning safely.<\/p>\n<h2 id=\"limits\">The limits are part of the design<\/h2>\n<p>This is macOS only. It depends on Claude Desktop, Electron user-data directories, LaunchServices, and launchd. It also requires one manual sign-in per profile. It can&#8217;t migrate accounts magically or bypass the browser authentication step.<\/p>\n<p>It&#8217;s also unofficial. I built it as a local utility around the files and launch behavior the repository documents, not as a replacement for any official account-management feature. If that trade-off fits your Mac, the <a href=\"https:\/\/github.com\/EminBudak\/claude-multi-account\" target=\"_blank\" rel=\"nofollow noopener\">project repository<\/a> has the full command reference, safety behavior, and current changes.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Multiple Claude accounts on one Mac were awkward enough that I built claude-multi-account. Claude Desktop keeps one signed-in account per app. Switching accounts clears the sign-in, and the&hellip;<\/p>\n","protected":false},"author":4,"featured_media":3940,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[311],"tags":[314,329,328],"class_list":["post-3942","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software-development","tag-claude","tag-developer-tools","tag-macos"],"_links":{"self":[{"href":"https:\/\/eminbudak.com\/blog\/wp-json\/wp\/v2\/posts\/3942","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/eminbudak.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/eminbudak.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/eminbudak.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/eminbudak.com\/blog\/wp-json\/wp\/v2\/comments?post=3942"}],"version-history":[{"count":2,"href":"https:\/\/eminbudak.com\/blog\/wp-json\/wp\/v2\/posts\/3942\/revisions"}],"predecessor-version":[{"id":3945,"href":"https:\/\/eminbudak.com\/blog\/wp-json\/wp\/v2\/posts\/3942\/revisions\/3945"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/eminbudak.com\/blog\/wp-json\/wp\/v2\/media\/3940"}],"wp:attachment":[{"href":"https:\/\/eminbudak.com\/blog\/wp-json\/wp\/v2\/media?parent=3942"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/eminbudak.com\/blog\/wp-json\/wp\/v2\/categories?post=3942"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/eminbudak.com\/blog\/wp-json\/wp\/v2\/tags?post=3942"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}