diff --git a/src/main/claude-accounts/environment.ts b/src/main/claude-accounts/environment.ts index 35dd3c1825a..07bc9764f4d 100644 --- a/src/main/claude-accounts/environment.ts +++ b/src/main/claude-accounts/environment.ts @@ -7,6 +7,7 @@ export const CLAUDE_AUTH_ENV_VARS = [ export type ClaudeEnvPatch = { CLAUDE_CONFIG_DIR?: string + ORCA_CLAUDE_CONFIG_DIR?: string ANTHROPIC_CUSTOM_HEADERS?: string } @@ -27,6 +28,9 @@ export function applyClaudeEnvPatch( if (patch.CLAUDE_CONFIG_DIR && !baseEnv.CLAUDE_CONFIG_DIR) { baseEnv.CLAUDE_CONFIG_DIR = patch.CLAUDE_CONFIG_DIR } + if (patch.ORCA_CLAUDE_CONFIG_DIR && !baseEnv.ORCA_CLAUDE_CONFIG_DIR) { + baseEnv.ORCA_CLAUDE_CONFIG_DIR = patch.ORCA_CLAUDE_CONFIG_DIR + } if (patch.ANTHROPIC_CUSTOM_HEADERS !== undefined) { baseEnv.ANTHROPIC_CUSTOM_HEADERS = patch.ANTHROPIC_CUSTOM_HEADERS } diff --git a/src/main/claude-accounts/runtime-auth-service.ts b/src/main/claude-accounts/runtime-auth-service.ts index 79b4fbcfe43..cfeabef9b58 100644 --- a/src/main/claude-accounts/runtime-auth-service.ts +++ b/src/main/claude-accounts/runtime-auth-service.ts @@ -1,3 +1,4 @@ +import { readFileSync } from 'node:fs' import type { Store } from '../persistence' import { getSelectedClaudeAccountIdForTarget, @@ -124,6 +125,17 @@ export class ClaudeRuntimeAuthService extends ClaudeRuntimeAuthSync { process.platform === 'darwin' ? () => readActiveClaudeKeychainCredentialsStrict() : undefined, + readLegacyOauthAccount: () => { + try { + const parsed = JSON.parse(readFileSync(paths.configPath, 'utf-8')) as Record< + string, + unknown + > + return parsed.oauthAccount ?? null + } catch { + return null + } + }, readManagedCredentials: (account) => this.readManagedCredentials(account), writeManagedCredentials: (account, contents) => this.writeManagedCredentials(account, contents) diff --git a/src/main/claude-accounts/runtime-auth/runtime-auth-preparation.ts b/src/main/claude-accounts/runtime-auth/runtime-auth-preparation.ts index 392727695bd..cd03ccbd35d 100644 --- a/src/main/claude-accounts/runtime-auth/runtime-auth-preparation.ts +++ b/src/main/claude-accounts/runtime-auth/runtime-auth-preparation.ts @@ -1,3 +1,4 @@ +import { existsSync, lstatSync, mkdirSync, symlinkSync } from 'node:fs' import { join } from 'node:path' import type { ClaudeManagedAccount } from '../../../shared/managed-account-types' import { resolveLocalAccountRuntimeTarget } from '../../../shared/local-account-runtime' @@ -44,7 +45,10 @@ export class ClaudeRuntimeAuthPreparationService extends ClaudeRuntimeAuthSnapsh runtime: 'wsl', wslDistro: activeAccount.wslDistro ?? null, wslLinuxConfigDir: activeAccount.wslLinuxAuthPath, - envPatch: { CLAUDE_CONFIG_DIR: activeAccount.wslLinuxAuthPath }, + envPatch: { + CLAUDE_CONFIG_DIR: activeAccount.wslLinuxAuthPath, + ORCA_CLAUDE_CONFIG_DIR: activeAccount.wslLinuxAuthPath + }, stripAuthEnv: true, provenance: `managed:${activeAccount.id}:wsl:${activeAccount.wslDistro ?? ''}` } @@ -84,12 +88,16 @@ export class ClaudeRuntimeAuthPreparationService extends ClaudeRuntimeAuthSnapsh { adoptLegacyMarker: true } ) if (managedPath) { + provisionClaudeManagedHome(paths.configDir, managedPath) return { configDir: managedPath, runtime: 'host', wslDistro: null, wslLinuxConfigDir: null, - envPatch: { CLAUDE_CONFIG_DIR: managedPath }, + envPatch: { + CLAUDE_CONFIG_DIR: managedPath, + ORCA_CLAUDE_CONFIG_DIR: managedPath + }, stripAuthEnv: true, provenance: `managed:${activeAccount.id}` } @@ -145,3 +153,26 @@ export class ClaudeRuntimeAuthPreparationService extends ClaudeRuntimeAuthSnapsh return defaultDistro ? { runtime: 'wsl', wslDistro: defaultDistro } : target } } + +// Keep account homes useful without copying credential-bearing runtime state. +// Missing user resources are linked once; existing files remain user-owned. +function provisionClaudeManagedHome(systemConfigDir: string, managedPath: string): void { + if (systemConfigDir === managedPath) { + return + } + const resources = ['settings.json', 'CLAUDE.md', 'projects', 'plugins'] as const + for (const name of resources) { + const source = join(systemConfigDir, name) + const target = join(managedPath, name) + if (!existsSync(source) || existsSync(target)) { + continue + } + try { + const sourceIsDirectory = lstatSync(source).isDirectory() + mkdirSync(managedPath, { recursive: true, mode: 0o700 }) + symlinkSync(source, target, sourceIsDirectory ? 'dir' : 'file') + } catch { + // Best effort: auth routing remains safe if an optional resource cannot link. + } + } +} diff --git a/src/main/claude-accounts/runtime-paths.ts b/src/main/claude-accounts/runtime-paths.ts index 350cdd08372..af90693f492 100644 --- a/src/main/claude-accounts/runtime-paths.ts +++ b/src/main/claude-accounts/runtime-paths.ts @@ -20,7 +20,7 @@ export class ClaudeRuntimePathResolver { configDir, credentialsPath: join(configDir, '.credentials.json'), configPath: this.resolveConfigPath(configDir, inheritedConfigDir), - envPatch: inheritedConfigDir ? { CLAUDE_CONFIG_DIR: configDir } : {} + envPatch: { CLAUDE_CONFIG_DIR: configDir, ORCA_CLAUDE_CONFIG_DIR: configDir } } } diff --git a/src/main/ipc/pty/ipc/spawn-preflight.ts b/src/main/ipc/pty/ipc/spawn-preflight.ts index 6b74b7b08a7..f5b4a5153f7 100644 --- a/src/main/ipc/pty/ipc/spawn-preflight.ts +++ b/src/main/ipc/pty/ipc/spawn-preflight.ts @@ -223,7 +223,10 @@ export async function preparePtyIpcSpawnPreflight(ctx: PtyIpcSpawnState): Promis ) try { ctx.claudeAuth = - ctx.isClaudeLaunch && ctx.deps.prepareClaudeAuth + !ctx.preAdoptedStablePane && + !args.connectionId && + (ctx.isClaudeLaunch || !ctx.launchCommand) && + ctx.deps.prepareClaudeAuth ? await ctx.deps.prepareClaudeAuth(initialSelectionTarget) : null } catch (error) { diff --git a/src/main/ipc/pty/runtime/spawn-preflight.ts b/src/main/ipc/pty/runtime/spawn-preflight.ts index 5468118070a..4b4ec4bcb73 100644 --- a/src/main/ipc/pty/runtime/spawn-preflight.ts +++ b/src/main/ipc/pty/runtime/spawn-preflight.ts @@ -131,7 +131,10 @@ export async function prepareRuntimePtySpawn( ctx.launchCommand = codexResumeLaunch.command try { ctx.claudeAuth = - ctx.isClaudeLaunch && ctx.deps.prepareClaudeAuth + !ctx.preAdoptedStablePane && + !args.connectionId && + (ctx.isClaudeLaunch || !ctx.launchCommand) && + ctx.deps.prepareClaudeAuth ? await ctx.deps.prepareClaudeAuth(ctx.codexSelectionTarget) : null } catch (error) { diff --git a/src/main/persistence/loading-store/normalize-loaded-global-settings.ts b/src/main/persistence/loading-store/normalize-loaded-global-settings.ts index fb5cc9f1fe8..eb625d1e140 100644 --- a/src/main/persistence/loading-store/normalize-loaded-global-settings.ts +++ b/src/main/persistence/loading-store/normalize-loaded-global-settings.ts @@ -12,6 +12,7 @@ import { readLegacySidekickFlag } from '../applying-settings/onboarding-normaliz import type { PersistedState } from '../../../shared/persisted-state-types' import type { PreparedLoadedTerminalSettings } from './prepare-loaded-terminal-settings' import type { PreparedLoadedProfileSettings } from './prepare-loaded-profile-settings' +import type { ClaudeManagedAccount } from '../../../shared/managed-account-types' export function normalizeLoadedGlobalSettings( parsed: PersistedState, @@ -58,6 +59,9 @@ export function normalizeLoadedGlobalSettings( // old default indistinguishable from a real opt-in. Preserve stored `true`; only // the default changed. ...stripRetiredGlobalSettings(parsed.settings), + claudeManagedAccounts: normalizeClaudeManagedAccountRuntimes( + parsed.settings?.claudeManagedAccounts ?? defaults.settings.claudeManagedAccounts + ), worktreeVisibilityDefaults: migratedExternalVisibility.defaults, prBotAuthorOverrides: normalizePRBotAuthorOverrides(parsed.settings?.prBotAuthorOverrides), // Why: v1.3.42 renamed the sidekick setting to pet; carry the old flag forward once so enabled users don't lose it. @@ -136,3 +140,11 @@ export function normalizeLoadedGlobalSettings( } } } + +export function normalizeClaudeManagedAccountRuntimes( + accounts: readonly ClaudeManagedAccount[] +): ClaudeManagedAccount[] { + return accounts.map((account) => + account.managedAuthRuntime === undefined ? { ...account, managedAuthRuntime: 'host' } : account + ) +}