Route Claude panes through a per-account CLAUDE_CONFIG_DIR

Mirrors the CODEX_HOME spawn-time mechanism: ORCA_CLAUDE_CONFIG_DIR joins the
positive overlay allowlist and each shell wrapper re-exports CLAUDE_CONFIG_DIR
after user rc files, so a hand-typed claude inherits the selected account.

WIP recovered from an interrupted worker; per-account home creation and the
shared-store migration are not implemented yet.
This commit is contained in:
Merge Sim
2026-08-31 19:44:36 -07:00
committed by Merge Sim
parent d768cc6fc5
commit 4d70e19e96
11 changed files with 65 additions and 3 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ export function applyClaudeEnvPatch(
}
}
if (patch.CLAUDE_CONFIG_DIR) {
if (patch.CLAUDE_CONFIG_DIR && !baseEnv.CLAUDE_CONFIG_DIR) {
baseEnv.CLAUDE_CONFIG_DIR = patch.CLAUDE_CONFIG_DIR
}
if (patch.ANTHROPIC_CUSTOM_HEADERS !== undefined) {
@@ -19,7 +19,15 @@ export class ClaudeRuntimeAuthService extends ClaudeRuntimeAuthSync {
target?: ClaudeAccountSelectionTarget
): Promise<ClaudeRuntimeAuthPreparation> {
const effectiveTarget = target ?? this.getDefaultAccountSelectionTarget()
await this.syncForCurrentSelection(effectiveTarget)
const settings = this.store.getSettings()
const selectedId = getSelectedClaudeAccountIdForTarget(settings, effectiveTarget)
const selected = selectedId
? 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') {
await this.syncForCurrentSelection(effectiveTarget)
}
return this.getPreparation(effectiveTarget)
}
@@ -10,6 +10,7 @@ import {
} from '../runtime-selection'
import { ClaudeRuntimeAuthSnapshotRestore } from './runtime-auth-snapshot-restore'
import type { ClaudeRuntimeAuthPreparation } from './runtime-auth-types'
import { resolveOwnedClaudeManagedAuthPath } from '../managed-auth-path'
export class ClaudeRuntimeAuthPreparationService extends ClaudeRuntimeAuthSnapshotRestore {
protected getPreparation(target?: ClaudeAccountSelectionTarget): ClaudeRuntimeAuthPreparation {
@@ -63,6 +64,24 @@ export class ClaudeRuntimeAuthPreparationService extends ClaudeRuntimeAuthSnapsh
provenance: `wsl:${normalizeClaudeAccountSelectionTarget(normalizedTarget).wslDistro ?? '__default__'}:system`
}
}
if (activeAccount?.managedAuthRuntime !== 'wsl' && activeAccount) {
const managedPath = resolveOwnedClaudeManagedAuthPath(
activeAccount.id,
activeAccount.managedAuthPath,
{ adoptLegacyMarker: true }
)
if (managedPath) {
return {
configDir: managedPath,
runtime: 'host',
wslDistro: null,
wslLinuxConfigDir: null,
envPatch: { CLAUDE_CONFIG_DIR: managedPath },
stripAuthEnv: true,
provenance: `managed:${activeAccount.id}`
}
}
}
return {
configDir: paths.configDir,
runtime: 'host',
@@ -23,6 +23,13 @@ export class ClaudeRuntimeAuthSync extends ClaudeRuntimeAuthPreparationService {
this.lastSyncedAccountId
)
this.managedRefreshDeferredByLivePtyAccountId = null
// Per-account Claude config dirs are self-contained; syncing them into the
// shared ~/.claude would recreate the stale-sibling race this isolation removes.
if (activeAccount?.managedAuthRuntime === 'host') {
this.clearLastWrittenRuntimeState()
this.lastSyncedAccountId = activeAccount.id
return
}
const previousManagedCredentialsJson = previousAccount
? await this.readManagedCredentials(previousAccount)
: null
@@ -14,6 +14,7 @@ export function getDaemonZshWrapperSpec(): ZshStartupHookSpec {
agentTeamsPath: true,
remoteCliBinDir: false,
codexHome: true,
claudeConfigDir: true,
codexLaunchPreflight: true
}
}
+1
View File
@@ -8,6 +8,7 @@ const POWERSHELL_OSC133_BOOTSTRAP = `# Orca OSC 133 shell integration for PowerS
if ($env:ORCA_OPENCODE_CONFIG_DIR) { $env:OPENCODE_CONFIG_DIR = $env:ORCA_OPENCODE_CONFIG_DIR }
if ($env:ORCA_MIMOCODE_HOME) { $env:MIMOCODE_HOME = $env:ORCA_MIMOCODE_HOME }
if ($env:ORCA_CODEX_HOME) { $env:CODEX_HOME = $env:ORCA_CODEX_HOME }
if ($env:ORCA_CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR = $env:ORCA_CLAUDE_CONFIG_DIR }
if ($ExecutionContext.SessionState.LanguageMode -eq "FullLanguage" -and
((-not (Test-Path variable:global:__OrcaOsc133State)) -or
@@ -52,6 +52,8 @@ __orca_restore_agent_teams_path
${getPosixOmpShellWrapper()}
# Why: Codex must keep using Orca's runtime CODEX_HOME after profile scripts.
[[ -n "\${ORCA_CODEX_HOME:-}" ]] && export CODEX_HOME="\${ORCA_CODEX_HOME}"
# Why: Claude must keep using Orca's runtime config directory after profile scripts.
[[ -n "\${ORCA_CLAUDE_CONFIG_DIR:-}" ]] && export CLAUDE_CONFIG_DIR="\${ORCA_CLAUDE_CONFIG_DIR}"
${getPosixCodexShellLaunchPreflight()}
# Why: emit OSC 133 C/D so terminal-command-lifecycle can drop stale agent
# status when the foreground command (e.g. an interrupted Claude/Codex CLI)
@@ -27,6 +27,7 @@ export function getLocalZshWrapperSpec(): ZshStartupHookSpec {
agentTeamsPath: true,
remoteCliBinDir: false,
codexHome: true,
claudeConfigDir: true,
codexLaunchPreflight: true
}
}
@@ -53,7 +53,20 @@ export function finalizeWindowsLocalPtySpawnEnvironment(args: {
}
if (env.CLAUDE_CONFIG_DIR) {
// Why: managed WSL Claude passes a Linux CLAUDE_CONFIG_DIR through wsl.exe; non-default vars need WSLENV import.
addWslEnvKeys(env, ['CLAUDE_CONFIG_DIR'])
const claudeConfigWslInfo = parseWslPath(env.CLAUDE_CONFIG_DIR)
if (claudeConfigWslInfo) {
if (plan.launchWslDistro && plan.launchWslDistro !== claudeConfigWslInfo.distro) {
delete env.CLAUDE_CONFIG_DIR
delete env.ORCA_CLAUDE_CONFIG_DIR
} else {
env.CLAUDE_CONFIG_DIR = claudeConfigWslInfo.linuxPath
env.ORCA_CLAUDE_CONFIG_DIR = claudeConfigWslInfo.linuxPath
addWslEnvKeys(env, ['CLAUDE_CONFIG_DIR', 'ORCA_CLAUDE_CONFIG_DIR'])
}
} else {
env.ORCA_CLAUDE_CONFIG_DIR ??= env.CLAUDE_CONFIG_DIR
addWslEnvKeys(env, ['CLAUDE_CONFIG_DIR', 'ORCA_CLAUDE_CONFIG_DIR'])
}
}
if (env[ORCA_HERMES_STARTUP_QUERY_ENV] !== undefined) {
// Why: wsl.exe drops custom Windows env vars; the startup wrapper needs this imported inside WSL.
@@ -65,6 +78,10 @@ export function finalizeWindowsLocalPtySpawnEnvironment(args: {
delete env.ORCA_CODEX_HOME
}
if (pathWin32.basename(plan.shellPath).toLowerCase() !== 'wsl.exe' && env.CLAUDE_CONFIG_DIR) {
env.ORCA_CLAUDE_CONFIG_DIR ??= env.CLAUDE_CONFIG_DIR
}
const shellBasename = pathWin32.basename(plan.shellPath).toLowerCase()
const codexLaunchPreflightCommand = env.ORCA_CODEX_LAUNCH_PREFLIGHT
if (
+1
View File
@@ -30,6 +30,7 @@ const OVERLAY_ENV_KEYS = [
'ORCA_MIMOCODE_HOME',
'ORCA_OMP_STATUS_EXTENSION',
'ORCA_CODEX_HOME',
'ORCA_CLAUDE_CONFIG_DIR',
'ORCA_AGENT_TEAMS_SHIM_DIR',
'ORCA_REMOTE_CLI_BIN_DIR'
] as const
+5
View File
@@ -46,6 +46,8 @@ export type ZshWrapperRestoreSpec = {
codexHome: boolean
/** The `codex()` wrapper that runs Orca's launch preflight. */
codexLaunchPreflight: boolean
/** Orca's runtime Claude config directory. */
claudeConfigDir: boolean
}
export type ZshStartupHookSpec = {
@@ -75,6 +77,8 @@ const MIMOCODE_HOME_RESTORE = `[[ -n "\${ORCA_MIMOCODE_HOME:-}" ]] && export MIM
const REMOTE_CLI_BIN_DIR_RESTORE = `[[ -n "\${ORCA_REMOTE_CLI_BIN_DIR:-}" ]] && case ":$PATH:" in *:"\${ORCA_REMOTE_CLI_BIN_DIR}":*) ;; *) export PATH="\${ORCA_REMOTE_CLI_BIN_DIR}:$PATH" ;; esac`
const CODEX_HOME_RESTORE = `# Why: Codex must keep using Orca's runtime CODEX_HOME after rc files.
[[ -n "\${ORCA_CODEX_HOME:-}" ]] && export CODEX_HOME="\${ORCA_CODEX_HOME}"`
const CLAUDE_CONFIG_DIR_RESTORE = `# Why: Claude must keep using Orca's runtime config directory after rc files.
[[ -n "\${ORCA_CLAUDE_CONFIG_DIR:-}" ]] && export CLAUDE_CONFIG_DIR="\${ORCA_CLAUDE_CONFIG_DIR}"`
/**
* The OSC 133 hooks, defined at top level so their bodies are parsed before the
@@ -125,6 +129,7 @@ function getOverlayRestoreBlocks(spec: ZshStartupHookSpec): (string | null)[] {
spec.restores.remoteCliBinDir ? REMOTE_CLI_BIN_DIR_RESTORE : null,
getPosixOmpShellWrapper(),
spec.restores.codexHome ? CODEX_HOME_RESTORE : null,
spec.restores.claudeConfigDir ? CLAUDE_CONFIG_DIR_RESTORE : null,
spec.restores.codexLaunchPreflight ? getPosixCodexShellLaunchPreflight() : null
]
}