Repository navigation
fix(claude-accounts): write oauthAccount to the config file Claude reads - #22668
AlgoraAndrew wants to merge 2 commits into
Conversation
Orca resolved the Claude config path to ~/.claude/.claude.json whenever that file existed, but Claude Code reads it only when CLAUDE_CONFIG_DIR is set and otherwise reads ~/.claude.json. Tools that run Claude with CLAUDE_CONFIG_DIR=~/.claude leave the colocated file behind, after which every managed account switch moved the credentials but wrote oauthAccount to a file the CLI never reads, so new terminals kept showing the previous account. Resolve the colocated file only when CLAUDE_CONFIG_DIR is inherited. The system-default snapshot now records its configPath. A snapshot captured from another path, or before paths were recorded, cannot prove which identity was the system's, so its oauthAccount is cleared before any runtime mutation and Claude repopulates it from its token's profile on the next start. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughThe runtime path resolver selects an existing Merge Risk: ⚪ Minimal · up to Account updates and restoration target the selected Claude config, with snapshots reconciled when that path changes. The supplied changes show no remaining merge-blocking risk. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: fcbcef96-b0f8-4b09-8800-de615510a8c3
📒 Files selected for processing (9)
src/main/claude-accounts/runtime-auth-service-config-path-upgrade.test.tssrc/main/claude-accounts/runtime-auth-service-config-path.test.tssrc/main/claude-accounts/runtime-auth-service.tssrc/main/claude-accounts/runtime-auth/runtime-auth-keychain-snapshots.tssrc/main/claude-accounts/runtime-auth/runtime-auth-snapshot-capture.tssrc/main/claude-accounts/runtime-auth/runtime-auth-sync.tssrc/main/claude-accounts/runtime-auth/runtime-auth-types.tssrc/main/claude-accounts/runtime-paths.test.tssrc/main/claude-accounts/runtime-paths.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
There was a problem hiding this comment.
ℹ️ Minor suggestion only — the resolver now matches Claude's
.claude.jsonrule, but not the.config.jsoncase.
Reviewed changes
- Resolver now keys off
CLAUDE_CONFIG_DIR—resolveConfigPathreturns<configDir>/.claude.jsononly when the variable is inherited, otherwise~/.claude.json; theexistsSyncfallback that let a colocated file redirect writes is gone. - Snapshots record their source file —
configPathadded toClaudeSystemDefaultSnapshot(runtime-auth-types.ts:18), written at capture (runtime-auth-snapshot-capture.ts:93), and accepted as optional by the shape validator (runtime-auth-keychain-snapshots.ts:24) so pre-upgrade snapshots stay readable. - New
reconcileSystemDefaultSnapshotConfigPath()— rewrites a snapshot whose recordedconfigPathdiffers (includingundefined) withconfigOauthAccount: nullplus the current path; runs before host mutation indoSyncForCurrentSelectionand inforceMaterializeCurrentSelectionForRollback. - Test coverage — two new files pin the resolver rule, materialize/restore per config path, and a set of legacy/path-changed snapshot migration histories with exact config contents.
I verified the load-bearing premise against the shipped @anthropic-ai/claude-code bundles: Claude's global-config resolver is existsSync(join(CLAUDE_CONFIG_DIR ?? ~/.claude, '.config.json')) ? that : join(CLAUDE_CONFIG_DIR ?? homedir(), '.claude.json'). That confirms the unset case reads ~/.claude.json and never the nested .claude.json, and that every host restore site is preceded by the reconcile. The suite passes (29 files, 238 tests). The one residual gap is inline.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
Claude Code reads <config dir>/.config.json before .claude.json when it exists (config dir = CLAUDE_CONFIG_DIR, else ~/.claude). Mirror that rule so oauthAccount lands in the file Claude reads in that case too. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
This run covers the delta since the prior pullfrog review — commit c46d6ed (honor Claude's legacy .config.json precedence), which closes the one finding from that review.
- Resolver honors Claude's legacy
.config.jsonprecedence —resolveConfigPath(runtime-paths.ts:27) now probes<configDir>/.config.jsonfirst, then falls back to the colocated.claude.jsonwhenCLAUDE_CONFIG_DIRis inherited and~/.claude.jsonotherwise. This mirrors Claude's exact ruleexistsSync(join(CLAUDE_CONFIG_DIR ?? ~/.claude, '.config.json')) ? that : join(CLAUDE_CONFIG_DIR ?? homedir(), '.claude.json')across all fourCLAUDE_CONFIG_DIR/ file-existence states. Snapshot capture and restore go through the same resolver, so the fixed path is used consistently. - Resolver test covers both override states — the new parameterized case (
runtime-paths.test.ts:125-142) writes both candidate files and asserts an exactconfigPath, which fails under the previous implementation in both branches; ran it locally (11 tests pass).
The prior review's thread on runtime-paths.ts:35 is resolved, and no new findings surfaced.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
|
Thanks for putting this together, and sorry for the slow turnaround. The area has since been reworked and this is now covered on main by #26801 (per-account CLAUDE_CONFIG_DIR and scoped Keychain items, replacing the old credential swap/OAuth refresh code), so we're closing this as superseded. If you still see the problem on the latest build, please open a fresh report or reopen with a current repro. Thank you for the contribution. |

ELI5
When you switch Claude accounts, Orca updates two things: the login token and a small "who am I" record (
oauthAccount). It wrote that record to a file Claude doesn't read, so new terminals kept showing the old account. Now Orca writes it where Claude actually looks.What Changed
ClaudeRuntimePathResolveruses$CLAUDE_CONFIG_DIR/.claude.jsononly whenCLAUDE_CONFIG_DIRis inherited, and otherwise~/.claude.json. This matches how Claude Code chooses its config file. The existence of~/.claude/.claude.jsonno longer redirects Orca's writes. Like Claude, the resolver prefers a legacy<config dir>/.config.jsonwhen it exists (added in review, thanks to pullfrog).configPathit was captured from.~/.claude/.claude.json, a file Claude never read. Before any runtime mutation,reconcileSystemDefaultSnapshotConfigPaththerefore sets that snapshot'sconfigOauthAccounttonulland records the current path.oauthAccountthrough the existing ownership-checked restore, instead of writing a possibly wrong identity.oauthAccountat startup fromGET /api/oauth/profilewith its current token.Before: after an account switch,
claude auth statusin new terminals showed the previous account whenever~/.claude/.claude.jsonexisted.After: new terminals show the selected account. Returning to the system default never restores a stale identity: at worst
oauthAccountis absent until Claude's next start refetches it.Why
Claude Code decides which config file to read by whether
CLAUDE_CONFIG_DIRis set, not by which file exists. I verified this on 2.1.281: with different identities in the two files, the CLI reports~/.claude.jsonwhen the variable is unset and$CLAUDE_CONFIG_DIR/.claude.jsonwhen it is set. Tools that run Claude withCLAUDE_CONFIG_DIR=~/.claude(for example claude-mem's observer, thedotmack/claude-mem#4149) leave the colocated file behind permanently.For snapshots taken under the old resolver, I first tried to infer the source and recapture the system identity from the current file. Independent review kept finding histories where the recaptured value was not the system identity: a system identity equal to a managed one, a colocated file created after the snapshot, a changed
CLAUDE_CONFIG_DIR, removed accounts, email casing, and unreadable or WSL-probed managed storage. Nothing on disk proves which identity was the system's. Clearing is the only choice that can never restore a wrong identity, and Claude self-heals it from the token's profile.Linked Issue
Fixes #22667
Visual Proof
N/A. No UI change; the behavior is covered by the tests below and the CLI probe in the issue.
Testing
I manually tested these changes locally
Automated tests added/updated, or explained why not below
npx vitest run src/main/claude-accounts: 29 files, 240 tests pass.pnpm typecheckexits 0.oxlintandoxfmtpass (also via the pre-commit hook).New tests:
runtime-paths.test.ts: the home config stays in use when a colocated file exists without an override; the colocated file is used whenCLAUDE_CONFIG_DIRis inherited; a legacy.config.jsontakes precedence in both cases.runtime-auth-service-config-path.test.ts: materialization and restore update only the file the CLI reads, with and without an explicit directory.runtime-auth-service-config-path-upgrade.test.ts: a stale identity is never restored for legacy or path-changed snapshots. The histories covered are:CLAUDE_CONFIG_DIR;Disabling the reconciliation makes the upgrade tests fail; reverting the resolver change makes the resolver and config-path tests fail.
Platform: macOS. Windows and WSL behavior is covered by unit tests only. WSL managed accounts keep their own
CLAUDE_CONFIG_DIR, and reconciliation runs only for the host runtime.Manual check: after a local repair to the same state, a fresh
claude auth statusreported the selected managed account.AI Disclosure
Diagnosis and implementation by Claude Opus 5.5 (Claude Code). Independent review by GPT-6 Astra (Codex) in fresh sessions, with human direction and approval.
Review
Six independent review rounds (GPT-6 Astra, a fresh session each). Rounds 1–5 raised R1–R9, all on snapshot migration; each was fixed or made moot by the final clear-on-unknown policy. Round 6 reported no findings of any severity and confirmed that R1–R9 no longer apply. A seventh round on the
.config.jsonfollow-up also reported no findings.Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred). Locally:pnpm typecheck, oxlint/oxfmt on the changed files, and the fullsrc/main/claude-accountssuite. Full lint, test and build are left to CI.🤖 Generated with Claude Code