[BUG] Desktop account switch does not isolate local state: app storage is not account-keyed and ~/.claude.json is never reconciled
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Signing out of one Claude account and into another in the desktop app does not isolate machine-local state. There are already several open issues describing individual symptoms of this, each filed separately. This report is for the shared root cause, with evidence from the on-disk state.
Two distinct mechanisms are involved.
1. Account partitioning of local state is applied inconsistently — present in some stores, absent in others.
Some on-disk stores are keyed by account UUID:
~/.config/Claude/claude-code-sessions/<accountUuid>/<orgUuid>/scheduled-tasks.json
~/.config/Claude/local-agent-mode-sessions/<accountUuid>/
Renderer-side state is not. Across the entire ~/.config/Claude/Local Storage log, exactly one string references any of the three account/org UUIDs in use on this machine. Entries are written unkeyed, so an account switch has nothing to invalidate. The stored project selection is representative — no account field anywhere in the record:
{"folder":"/home/<user>/<work-project-path>","folderByHost":{},"currentHostKey":"local"}
So the concept of per-account storage exists in the codebase and is simply not applied to the state that produces the reported symptoms.
A teardown routine also exists: #78812 identifies stripAccountSlice resetting customGroups on switch. Its coverage is partial, and it fails in both directions — some state that should be cleared persists, and some state that should persist is destroyed.
The clearest illustration is that teardown is selective between two Chromium stores in the same profile. Switching accounts recreates the claude.ai IndexedDB database:
IndexedDB/https_claude.ai_0.indexeddb.leveldb/
CURRENT, LOCK, LOG, MANIFEST-000001 ← all stamped at the moment of the switch
000003.log ← only data file, no .ldb files
MANIFEST-000001 with no .ldb files is a newly created database. The associated blob directory is recreated at the same time. Meanwhile Local Storage in that same profile is untouched by the switch — it still carries its original CURRENT and LOCK stamps from when the profile was first created weeks earlier, along with an .ldb from that date.
So cached conversation data is discarded on an account switch, while the renderer state responsible for the symptoms in this report survives it. The isolation boundary runs between storage backends rather than around the account.
The account-keyed stores are not correctly keyed either. Every account switch deterministically creates a directory pairing the incoming org with the outgoing account UUID, under an account that is not a member of that organization. Sub-second timestamps show the race on a switch from the personal account to the work account:
19:39:54.447 mkdir claude-code-sessions/<personal-account>/<work-org>/
19:39:54.450 write claude-code-sessions/<personal-account>/<work-org>/scheduled-tasks.json
19:39:55.345 mkdir claude-code-sessions/<work-account>/<work-org>/
The incoming org identity is combined with the outgoing account identity and written to disk roughly 0.9 s before the correct pairing is written. local-agent-mode-sessions/ reproduces this identically at the same timestamps.
Switching back produces the mirror image, so this is directional rather than specific to one account:
20:11:34 mkdir claude-code-sessions/<work-account>/<personal-org>/
20:11:34 mkdir local-agent-mode-sessions/<work-account>/<personal-org>/
After two switches, four account/org directory pairs exist on this machine and two of them are combinations that should not be constructible — the personal account paired with the work org, and the work account paired with the personal org. One is created per switch, in whichever direction the switch goes.
No data was exposed here — all three scheduled-tasks.json files are 87-byte empty scaffolds (scheduledTasks: [], recordedSkips: {}), because no scheduled tasks exist on this machine. The defect is the path, and by extension the identity used to construct it: had tasks been defined, this code path would have written one organization's task state under a non-member account's directory.
A third store in the same parent directory pairs correctly, using the reverse nesting order:
local-agent-mode-sessions/skills-plugin/<work-org>/<work-account>
local-agent-mode-sessions/skills-plugin/<personal-org>/<personal-account>
Three stores, two nesting conventions, one incorrect pairing — consistent with account and org identity being read from different sources during a switch rather than from a single reconciled value.
2. ~/.claude.json has a single oauthAccount slot that no surface reconciles.
Whichever surface logged in most recently overwrites it, and the identity injected into a Claude Code session's context is read from that record rather than from the credentials actually authenticating the session. #78838 documents this mechanism.
The file currently holds state for two different organizations simultaneously, with no partitioning:
| Key | Value | Belongs to |
| --- | --- | --- |
| oauthAccount.accountUuid / organizationUuid | 179f04c7… / e1229fb6… | account B (personal) |
| cachedUsageUtilization.accountUuid | 89a069e8… | a third account UUID, neither of the above |
| clientDataCacheSlots | 9 slots under org a131aa41…, 2 under e1229fb6… | two orgs interleaved |
Timeline from file timestamps on this machine (times UTC):
08:45—~/.claude/.credentials.jsonlast written, authenticating CLI sessions as account A (work).10:04— account B's subscription created (subscriptionCreatedAt).15:23— desktop app logsaccount-change reevaluate: <none> → e1229fb6…:179f04c7…and~/.claude.jsonoauthAccountis rewritten to account B.
After 15:23, CLI sessions continued to authenticate as account A — credentials were untouched — while the userEmail injected into their context became account B's address. A session doing work under one account reports the other account's identity to the model.
What Should Happen?
- Account-scoped local state (project selection, usage aggregates, sidebar grouping, session lists) should be partitioned by account or cleared on switch, rather than written unkeyed and inherited by the next account to sign in.
- The identity injected into a session's context should be derived from the credentials that actually authenticate and bill that session, not from a
~/.claude.jsonrecord another surface may have overwritten. ~/.claude.jsonshould not accumulate cache entries for multiple organizations in one unpartitioned file. Two surfaces signed into different accounts are concurrently writing it, which also intersects with the corruption reported in #78439.
Error Messages/Logs
# ~/.config/Claude/logs/main.log — app switches identity and rewrites shared config
[info] [account] Account details received via IPC
[info] [sessions-bridge] account-change reevaluate: <none> → e1229fb6…:179f04c7…
[info] [LocalSessionManager] Initialization succeeded — accountId=179f04c7…, orgId=e1229fb6…
# the same log re-checking a working directory that belongs to the other account's work
[info] LocalSessions.checkTrust: cwd=/home/<user>/<work-project-path>
Steps to Reproduce
- On a machine that has been used with account A (via CLI or the desktop app), build up some history: a few sessions in one or more project directories.
- Sign into the desktop app with account B, a different account with no prior usage.
- Open the Code tab Overview panel. Account A's aggregates are shown as account B's — sessions, messages, token totals, active days, streak, heatmap (#75777).
- Check the working-directory chip on the new-session composer. It still points at the directory last used under account A. A different directory can be selected manually, but the account switch itself does not reset the selection (#83973).
- Inspect
~/.claude.json.oauthAccountnow describes account B, whileclientDataCacheSlotsandcachedUsageUtilizationstill carry account A's org and account UUIDs. - Start a Claude Code session authenticated as account A (for example from the VS Code extension, whose credentials were not touched by the app login) and ask it what account it is running under. It reports account B's email, read from
~/.claude.json. - List
~/.config/Claude/claude-code-sessions/and~/.config/Claude/local-agent-mode-sessions/two levels deep, with timestamps. Each is<accountUuid>/<orgUuid>/. Switch accounts again and re-list: a new directory appears pairing the incoming org with the outgoing account UUID, timestamped at the switch and preceding the correct pairing by about a second.
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
Claude Code Version
2.1.222 (embedded in Claude Desktop 1.26832.0); VS Code extension 2.1.226
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Other
Additional Information
Ubuntu 26.04 LTS. Surfaces involved are the Claude Desktop app and the VS Code extension, not a terminal.
Open issues that appear to be symptoms of this root cause:
- #78838 — session context reports a stale identity from
~/.claude.jsonrather than the owning account (the identity half of this report) - #75777 — usage stats panel shows the other account's aggregates
- #83973 — selected local project/repo doesn't reset on switch
- #73295 — sidebar sessions merged into one group after switching
- #78812 — custom session groups permanently wiped (
stripAccountSlice); the over-deletion direction - #79810 — custom sidebar groups disappear after switching and back
- #82922 and #83518 — Chrome extension connection broken by an account switch / mid-session re-login
Related but distinct: #81278 (CLAUDE_CONFIG_DIR not honored) covers the CLI-side isolation escape hatch, and #78439 covers concurrent-write corruption of ~/.claude.json, which two signed-in surfaces make more likely.
On scope: as far as I can tell this is local-only. #78838's logout/login experiment found that session ownership, billing, and plan quotas did follow the app login correctly, and I have seen no evidence of cross-account leakage server-side. What does not hold is local separation — one ~/.claude/projects transcript directory, one file-history/ snapshot store, and one credential slot shared by every account used on the machine. For anyone keeping work and personal accounts on one machine, transcripts and file snapshots from one account's work remain readable to sessions run under the other.
_Drafted by Claude (Anthropic AI assistant)._