Desktop app: session context reports a stale identity from ~/.claude.json, not the account that actually owns and bills the session

Status Open
Reported on v2.1.209
Maintainer reply None cached
Activity 4 comments · opened Jul 18, 2026

Desktop app: session context reports a stale identity from ~/.claude.json, not the account that actually owns and bills the session

Correction (same day): originally filed claiming the inverse — that sessions billed to the stale account. Testing disproved that. Corrected version below.

Environment

  • Claude desktop app (macOS, Darwin 25.5.0), embedded Claude Code 2.1.209
  • App UI signed in as: personal account (Pro plan)
  • ~/.claude.json oauthAccount: work account (Claude Team org) — stale, from an

earlier CLI login on the same machine

Summary

The desktop app login correctly controls session ownership and billing. But the
Claude Code session context reports a different, stale identity — the
userEmail injected into the model's context comes from ~/.claude.json's
oauthAccount, a record left behind by the machine's original CLI login weeks
earlier, which the app never reconciles or updates.

Verified behavior (logout/login experiment)

  1. Signed into app with personal account: session usage panel shows

"Plan usage limits · Pro" with that plan's quotas and reset times.

  1. Signed out, signed into work (Team) account: panel shows

"Your usage limits · Team" with entirely different quotas/reset schedule.
The personal session is not accessible under this login.

  1. Signed back into personal: session reappears.
  2. Throughout all of this, ~/.claude.json oauthAccount (work identity,

profileFetchedAt timestamp byte-identical) never changed — the app never
touches it.

  1. Yet every session under the personal login has userEmail: <work email> in

its system context.

So: billing ✓ correct, session scoping ✓ correct, identity in model context ✗ wrong.

Why this matters

  1. The model reasons from a false identity. In our session, Claude read the

work email from context, cross-checked ~/.claude.json (same stale record —
circular confirmation), and confidently concluded personal sessions were
billing the employer's Team seat. The user had to disprove Claude with
usage-panel screenshots. Wrong-identity context poisons anything downstream:
git identity suggestions, org/policy assumptions, personalization, memory.

  1. Users who inspect config are misled the same way. ~/.claude.json is the

documented home of the CLI's account state; a stale oauthAccount there looks
authoritative and implies billing goes somewhere it doesn't.

  1. No reconciliation. App login and the CLI credential record can drift

forever; nothing detects or surfaces the mismatch.

Expected behavior

  • Session context userEmail should come from the live app login, or be omitted.
  • App login should update/reconcile (or invalidate) ~/.claude.json's

oauthAccount, or the stale record should be clearly marked non-authoritative.

Repro

  1. Log into Claude Code CLI (terminal) with account A → ~/.claude.json stores A.
  2. Sign into the desktop app with account B.
  3. Start a Code session in the app → session is owned/billed by B (correct), but

its system context reports account A's email, and ~/.claude.json still
claims A indefinitely.

View original on GitHub ↗

3 Comments

m-hoss · 1 month ago

Correction: I've edited the title and body — the original filing claimed billing followed the stale ~/.claude.json credential (i.e., personal sessions consuming the employer Team seat). A logout/login experiment disproved that: the usage panel showed the correct plan (Pro vs Team, different quotas and reset schedules) under each login, and sessions are only accessible under the login that created them. Billing and session ownership follow the app login correctly.

What remains is the identity bug now described in the body: the session's system-context userEmail comes from a stale oauthAccount in ~/.claude.json (left by an old CLI login, never reconciled by the app), so the model is told the wrong account identity. Notably, that false context caused Claude itself to confidently mis-diagnose the billing direction — the original, wrong version of this issue was drafted from that mis-diagnosis.

m-hoss · 1 month ago

Additional finding — the app already knows the correct identity; it's just not what gets injected into context.

The desktop app writes lastKnownAccountUuid into its own config at
~/Library/Application Support/Claude/config.json on login. On this machine, after the logout/login cycle described above, that field holds the personal account's UUID (it matches the oauthAccount.accountUuid a CLI login records for that account), while ~/.claude.json still holds the stale work identity. Org-scoped keys in the same file (dxt:allowlistLastUpdated:<orgUuid>) were refreshed for the work org and then the personal org 37 seconds apart, timestamping the account switch.

So at session start the harness has a locally available, app-maintained record of the active account — but session context userEmail is sourced from the stale ~/.claude.json oauthAccount instead.

Workaround for affected users: ignore session-context userEmail and ~/.claude.json; read lastKnownAccountUuid from the app's config.json and map it to your accounts (each account's UUID appears as oauthAccount.accountUuid after a CLI login under that account).

Suggested fix: populate session context from the same source the app already maintains — or omit userEmail entirely when the app login and the CLI credential record disagree, rather than silently reporting the stale one.

willmcginnis · 1 month ago

The context-injection staleness you documented shares its root cause with the /status/banner display bug — root-caused with a deterministic repro in #79222: display and context surfaces read the oauthAccount cache in .claude.json (last writer wins: /login, plus a ~daily TTL-gated profile refresh), while the session's credential is resolved independently. Your lastKnownAccountUuid finding is referenced there as a locally available source of truth the injection path ignores.

Showing cached comments. Read the full discussion on GitHub ↗