From bf41b13cb661ced1e8fbb2f4b8e3ae771c5edc60 Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:28:29 -0700 Subject: [PATCH] Allow Claude account switches with unverified live auth (#5217) --- src/main/claude-accounts/runtime-auth-service.test.ts | 11 ++++++----- src/main/claude-accounts/runtime-auth-service.ts | 8 ++++++-- src/main/claude-accounts/service.test.ts | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/main/claude-accounts/runtime-auth-service.test.ts b/src/main/claude-accounts/runtime-auth-service.test.ts index e8a072fa7..6e95bf143 100644 --- a/src/main/claude-accounts/runtime-auth-service.test.ts +++ b/src/main/claude-accounts/runtime-auth-service.test.ts @@ -2872,7 +2872,7 @@ describe('ClaudeRuntimeAuthService', () => { expect(readFileSync(runtimeCredentialsPath, 'utf-8')).toBe(account2Credentials) }) - it('does not clobber unverified live runtime credentials when switching accounts', async () => { + it('switches accounts without persisting unverified live runtime credentials', async () => { const runtimeCredentialsPath = join(testState.fakeHomeDir, '.claude', '.credentials.json') const account1Original = createClaudeCredentialsJson('one@example.com', 'one-original', 'org-a') const unverifiedLiveCredentials = createClaudeCredentialsWithoutEmail('one-live', 'org-b') @@ -2912,15 +2912,16 @@ describe('ClaudeRuntimeAuthService', () => { writeFileSync(runtimeCredentialsPath, unverifiedLiveCredentials, 'utf-8') settings.activeClaudeManagedAccountId = 'account-2' - await expect(service.syncForCurrentSelection()).rejects.toThrow( - 'live Claude terminal has unverified refreshed auth' - ) + await service.syncForCurrentSelection() } finally { markClaudePtyExited('live-claude-pty') } expect(readManagedCredentialsForTest('account-1', managedAuthPath1)).toBe(account1Original) - expect(readFileSync(runtimeCredentialsPath, 'utf-8')).toBe(unverifiedLiveCredentials) + expect(readFileSync(runtimeCredentialsPath, 'utf-8')).toBe(account2Credentials) + if (process.platform === 'darwin') { + expect(testState.scopedKeychainCredentials).toBe(account2Credentials) + } }) it('routes refreshed Claude credentials to the matching managed account', async () => { diff --git a/src/main/claude-accounts/runtime-auth-service.ts b/src/main/claude-accounts/runtime-auth-service.ts index 90656c9f1..8d09a9d8d 100644 --- a/src/main/claude-accounts/runtime-auth-service.ts +++ b/src/main/claude-accounts/runtime-auth-service.ts @@ -211,8 +211,12 @@ export class ClaudeRuntimeAuthService { outgoingReadBackResult.runtimeCredentialsJson ) } else { - throw new Error( - 'Claude account switch paused because a live Claude terminal has unverified refreshed auth.' + // Why: Claude's runtime credential blob can lack enough identity + // proof to attribute a live-session refresh. Do not persist that + // unverified blob, but also do not block the user from moving new + // terminals to the selected managed account. + console.warn( + '[claude-runtime-auth] Skipping unverified live Claude auth read-back while switching accounts' ) } } diff --git a/src/main/claude-accounts/service.test.ts b/src/main/claude-accounts/service.test.ts index 4cbfe5bf1..454067ad5 100644 --- a/src/main/claude-accounts/service.test.ts +++ b/src/main/claude-accounts/service.test.ts @@ -803,7 +803,7 @@ describe('ClaudeAccountService credential capture', () => { } const runtimeAuth = { syncForCurrentSelection: vi.fn(async () => { - throw new Error('unverified live auth') + throw new Error('runtime sync failed') }), forceMaterializeCurrentSelectionForRollback: vi.fn(async () => {}) } @@ -817,7 +817,7 @@ describe('ClaudeAccountService credential capture', () => { runtimeAuth as never ) - await expect(service.selectAccount('account-2')).rejects.toThrow('unverified live auth') + await expect(service.selectAccount('account-2')).rejects.toThrow('runtime sync failed') expect(settings.activeClaudeManagedAccountId).toBe('account-1') expect(settings.activeClaudeManagedAccountIdsByRuntime).toEqual({