mirror of
https://github.com/stablyai/orca.git
synced 2026-10-09 08:02:35 +00:00
fix(claude-auth): preserve legacy shared runtime compatibility
This commit is contained in:
@@ -14,8 +14,9 @@ export class ClaudeRuntimeAuthService extends ClaudeRuntimeAuthSync {
|
||||
constructor(store: Store) {
|
||||
super(store)
|
||||
this.initializeLastSyncedState()
|
||||
void this.safeSyncForCurrentSelection()
|
||||
void this.migrateLegacySharedAuth()
|
||||
// Sync the selected runtime first; migration must not race a cleanup and
|
||||
// repopulate a managed account from a stale shared Keychain entry.
|
||||
void this.safeSyncForCurrentSelection().finally(() => this.migrateLegacySharedAuth())
|
||||
}
|
||||
|
||||
async prepareForClaudeLaunch(
|
||||
@@ -28,7 +29,32 @@ export class ClaudeRuntimeAuthService extends ClaudeRuntimeAuthSync {
|
||||
? settings.claudeManagedAccounts.find((account) => account.id === selectedId)
|
||||
: null
|
||||
// Isolated accounts are already Claude's runtime store; never copy them into ~/.claude.
|
||||
if (!selected || selected.managedAuthRuntime === 'wsl') {
|
||||
// Legacy accounts with valid credentials still use the shared runtime and
|
||||
// must be synchronized before launch; missing legacy credentials are left
|
||||
// for the background cleanup path to handle without a second restore.
|
||||
const legacyCredentials =
|
||||
selected && selected.managedAuthRuntime === undefined
|
||||
? await this.readManagedCredentials(selected)
|
||||
: null
|
||||
let cleanupMissingLegacy = false
|
||||
if (selected && selected.managedAuthRuntime === undefined && legacyCredentials === null) {
|
||||
const runtimeCredentials = this.readRuntimeCredentialsFile()
|
||||
const managedOauth = await this.readManagedOauthAccount(selected)
|
||||
const runtimeMatches = this.runtimeCredentialsBelongToAccount(
|
||||
runtimeCredentials,
|
||||
selected,
|
||||
managedOauth
|
||||
)
|
||||
cleanupMissingLegacy = runtimeMatches
|
||||
}
|
||||
if (
|
||||
!selected ||
|
||||
selected.managedAuthRuntime === 'wsl' ||
|
||||
(selected.managedAuthRuntime === undefined &&
|
||||
legacyCredentials !== null &&
|
||||
this.isValidCredentialsJsonObject(legacyCredentials)) ||
|
||||
cleanupMissingLegacy
|
||||
) {
|
||||
await this.syncForCurrentSelection(effectiveTarget)
|
||||
}
|
||||
return this.getPreparation(effectiveTarget)
|
||||
|
||||
@@ -77,7 +77,7 @@ export class ClaudeRuntimeAuthPreparationService extends ClaudeRuntimeAuthSnapsh
|
||||
provenance: `wsl:${normalizeClaudeAccountSelectionTarget(normalizedTarget).wslDistro ?? '__default__'}:system`
|
||||
}
|
||||
}
|
||||
if (activeAccount?.managedAuthRuntime !== 'wsl' && activeAccount) {
|
||||
if (activeAccount?.managedAuthRuntime === 'host') {
|
||||
const managedPath = resolveOwnedClaudeManagedAuthPath(
|
||||
activeAccount.id,
|
||||
activeAccount.managedAuthPath,
|
||||
@@ -101,14 +101,14 @@ export class ClaudeRuntimeAuthPreparationService extends ClaudeRuntimeAuthSnapsh
|
||||
wslDistro: null,
|
||||
wslLinuxConfigDir: null,
|
||||
envPatch: paths.envPatch,
|
||||
stripAuthEnv: Boolean(activeAccountId && activeAccount?.managedAuthRuntime !== 'wsl'),
|
||||
stripAuthEnv: Boolean(activeAccountId && activeAccount?.managedAuthRuntime === 'host'),
|
||||
managedRefreshDeferredByLivePty: Boolean(
|
||||
activeAccountId &&
|
||||
activeAccount?.managedAuthRuntime !== 'wsl' &&
|
||||
activeAccount?.managedAuthRuntime === 'host' &&
|
||||
this.managedRefreshDeferredByLivePtyAccountId === activeAccountId
|
||||
),
|
||||
provenance:
|
||||
activeAccountId && activeAccount?.managedAuthRuntime !== 'wsl'
|
||||
activeAccountId && activeAccount?.managedAuthRuntime === 'host'
|
||||
? `managed:${activeAccountId}`
|
||||
: 'system'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { startSpan } from '../../observability/tracer'
|
||||
import { writeActiveClaudeKeychainCredentialsForRuntime } from '../keychain'
|
||||
import { ClaudeRuntimeAuthCredentialMatching } from './runtime-auth-credential-matching'
|
||||
import type {
|
||||
ClaudeReadBackMatch,
|
||||
@@ -113,8 +114,10 @@ export class ClaudeRuntimeAuthReadback extends ClaudeRuntimeAuthCredentialMatchi
|
||||
if (options.updateLastWrittenCredentialsJson) {
|
||||
this.writeRuntimeCredentials(runtimeContents)
|
||||
this.lastWrittenCredentialsJson = runtimeContents
|
||||
// The managed account remains the source of truth; do not write the
|
||||
// shared active Keychain service for an isolated account.
|
||||
if (process.platform === 'darwin' && match.account.managedAuthRuntime === undefined) {
|
||||
const paths = this.pathResolver.getRuntimePaths()
|
||||
await writeActiveClaudeKeychainCredentialsForRuntime(runtimeContents, paths.configDir)
|
||||
}
|
||||
}
|
||||
decisionSpan.end()
|
||||
return { status: 'persisted' }
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '../runtime-selection'
|
||||
import { hasLiveClaudePtys } from '../live-pty-gate'
|
||||
import { isOauthTokenExpiring } from '../oauth-refresh'
|
||||
import { writeActiveClaudeKeychainCredentialsForRuntime } from '../keychain'
|
||||
import { ClaudeRuntimeAuthPreparationService } from './runtime-auth-preparation'
|
||||
|
||||
export class ClaudeRuntimeAuthSync extends ClaudeRuntimeAuthPreparationService {
|
||||
@@ -142,7 +143,17 @@ export class ClaudeRuntimeAuthSync extends ClaudeRuntimeAuthPreparationService {
|
||||
console.warn(
|
||||
'[claude-runtime-auth] Active managed account is not owned by Orca, restoring system default'
|
||||
)
|
||||
if (this.lastSyncedAccountId !== null) {
|
||||
if (activeAccount.managedAuthRuntime === undefined) {
|
||||
// Legacy accounts cannot safely restore a missing managed credential;
|
||||
// retain the shared runtime while ensuring a valid snapshot marker.
|
||||
if (!this.readSystemDefaultSnapshot(this.getSystemDefaultSnapshotPath())) {
|
||||
await this.captureSystemDefaultSnapshot({ force: false })
|
||||
}
|
||||
}
|
||||
if (
|
||||
this.lastSyncedAccountId !== null &&
|
||||
(activeAccount.managedAuthRuntime !== undefined || previousAccount?.id !== activeAccount.id)
|
||||
) {
|
||||
if (
|
||||
previousAccount &&
|
||||
(previousAccount.id !== activeAccount.id ||
|
||||
@@ -167,7 +178,15 @@ export class ClaudeRuntimeAuthSync extends ClaudeRuntimeAuthPreparationService {
|
||||
console.warn(
|
||||
'[claude-runtime-auth] Active managed account is missing or has invalid credentials, restoring system default'
|
||||
)
|
||||
if (this.lastSyncedAccountId !== null) {
|
||||
if (activeAccount.managedAuthRuntime === undefined) {
|
||||
if (!this.readSystemDefaultSnapshot(this.getSystemDefaultSnapshotPath())) {
|
||||
await this.captureSystemDefaultSnapshot({ force: false })
|
||||
}
|
||||
}
|
||||
if (
|
||||
this.lastSyncedAccountId !== null &&
|
||||
(activeAccount.managedAuthRuntime !== undefined || previousAccount?.id !== activeAccount.id)
|
||||
) {
|
||||
if (
|
||||
previousAccount &&
|
||||
(previousAccount.id !== activeAccount.id ||
|
||||
@@ -263,9 +282,21 @@ export class ClaudeRuntimeAuthSync extends ClaudeRuntimeAuthPreparationService {
|
||||
}
|
||||
|
||||
this.writeRuntimeCredentials(credentialsJson)
|
||||
// Isolated accounts are self-contained config roots. Never mirror their
|
||||
// credentials into either active Keychain service (the legacy service is
|
||||
// shared by all accounts and creates stale siblings).
|
||||
// Legacy accounts predate per-account runtime isolation and still need both
|
||||
// Keychain services for old and new Claude Code builds. Isolated accounts
|
||||
// must never touch the shared active services.
|
||||
if (process.platform === 'darwin' && activeAccount.managedAuthRuntime === undefined) {
|
||||
const paths = this.pathResolver.getRuntimePaths()
|
||||
try {
|
||||
await writeActiveClaudeKeychainCredentialsForRuntime(credentialsJson, paths.configDir)
|
||||
} catch (error) {
|
||||
await this.restoreSystemDefaultSnapshot(
|
||||
credentialsJson,
|
||||
await this.readManagedOauthAccount(activeAccount)
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
const managedOauthAccount = await this.readManagedOauthAccount(activeAccount)
|
||||
if (this.writeRuntimeOauthAccount(managedOauthAccount)) {
|
||||
this.lastWrittenOauthAccount = managedOauthAccount
|
||||
|
||||
Reference in New Issue
Block a user