refactor(codex): delete the unreachable managed shared-mirror lane (#12614)
PR 9501 shipped real-home routing for the host system default, and the env override that could turn it back off was never a shipped control. The managed-account half of the shared runtime mirror has been unreachable since: every host account routes to its own self-contained CODEX_HOME before that code runs. Delete the flag module and its env plumbing plus the managed branch of syncForCurrentSelection and the six helpers only it called. The three lanes that still use the shared mirror -- Windows, a custom CODEX_HOME, and a hook-lane gate that reports unusable -- are untouched, as are every legacy migration and the WSL read-back helpers.
This commit is contained in:
parent
38ba22ecd1
commit
4c49989c2e
|
|
@ -35,7 +35,6 @@ const RESTRICTED_ENV_KEYS = [
|
|||
'HOMEPATH',
|
||||
'CODEX_HOME',
|
||||
'ORCA_CODEX_HOME',
|
||||
'ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME',
|
||||
'ORCA_E2E_HOME_DIR',
|
||||
'ORCA_E2E_USER_DATA_DIR',
|
||||
'ORCA_USER_DATA_PATH',
|
||||
|
|
@ -69,7 +68,7 @@ async function resolveRealPath(candidate) {
|
|||
}
|
||||
}
|
||||
|
||||
export function createValidationEnv(inheritedEnv, layout, options = {}) {
|
||||
export function createValidationEnv(inheritedEnv, layout) {
|
||||
const env = { ...inheritedEnv }
|
||||
for (const key of RESTRICTED_ENV_KEYS) {
|
||||
delete env[key]
|
||||
|
|
@ -81,12 +80,7 @@ export function createValidationEnv(inheritedEnv, layout, options = {}) {
|
|||
NODE_ENV: 'development',
|
||||
ORCA_E2E_HOME_DIR: layout.homeDir,
|
||||
ORCA_E2E_USER_DATA_DIR: layout.userDataDir,
|
||||
ORCA_USER_DATA_PATH: layout.userDataDir,
|
||||
// Why: flag OFF pins every codex spawn to an explicit managed CODEX_HOME,
|
||||
// so native codex never resolves the OS profile — the only Windows
|
||||
// configuration where strict zero-event containment is reachable. It also
|
||||
// exercises the emergency kill-switch lane users fall back to.
|
||||
ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME: options.systemDefaultRealHome === 'off' ? '0' : '1'
|
||||
ORCA_USER_DATA_PATH: layout.userDataDir
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -268,8 +262,7 @@ function parseArgs(argv) {
|
|||
primaryHome: os.homedir(),
|
||||
configTemplate: null,
|
||||
tempParent: null,
|
||||
laneAwareContainment: false,
|
||||
systemDefaultRealHome: 'on'
|
||||
laneAwareContainment: false
|
||||
}
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index]
|
||||
|
|
@ -299,14 +292,8 @@ function parseArgs(argv) {
|
|||
options.skipBuild = true
|
||||
} else if (arg === '--keep') {
|
||||
options.keep = true
|
||||
} else if (arg === '--system-default-real-home') {
|
||||
const value = readValue()
|
||||
if (value !== 'on' && value !== 'off') {
|
||||
throw new Error('--system-default-real-home must be "on" or "off"')
|
||||
}
|
||||
options.systemDefaultRealHome = value
|
||||
} else if (arg === '--lane-aware-containment') {
|
||||
// Why: on Windows the flag-ON system-default lane cannot be env-sandboxed
|
||||
// Why: on Windows the system-default real-home lane cannot be env-sandboxed
|
||||
// (native codex ignores USERPROFILE), so strict zero-event containment is
|
||||
// structurally unreachable there. This mode records codex's designed
|
||||
// volatile churn without aborting while every other real-home write stays
|
||||
|
|
@ -314,7 +301,7 @@ function parseArgs(argv) {
|
|||
options.laneAwareContainment = true
|
||||
} else if (arg === '--help') {
|
||||
console.log(
|
||||
'Usage: node config/scripts/run-codex-real-account-validation.mjs [--scenario mixed|managed-only|codex-lb] [--config-template <path>] [--temp-parent <dir>] [--skip-build] [--dry-run] [--close-after-launch] [--keep] [--lane-aware-containment] [--system-default-real-home on|off] [--report <path>]'
|
||||
'Usage: node config/scripts/run-codex-real-account-validation.mjs [--scenario mixed|managed-only|codex-lb] [--config-template <path>] [--temp-parent <dir>] [--skip-build] [--dry-run] [--close-after-launch] [--keep] [--lane-aware-containment] [--report <path>]'
|
||||
)
|
||||
process.exit(0)
|
||||
} else {
|
||||
|
|
@ -480,7 +467,7 @@ async function main() {
|
|||
const reportPath =
|
||||
options.reportPath ??
|
||||
path.join(os.tmpdir(), `orca-codex-real-account-${options.scenario}-${Date.now()}.json`)
|
||||
const launchEnv = createValidationEnv(process.env, layout, options)
|
||||
const launchEnv = createValidationEnv(process.env, layout)
|
||||
let app = null
|
||||
let tripwire = null
|
||||
const abortController = new AbortController()
|
||||
|
|
|
|||
|
|
@ -46,29 +46,9 @@ describe('Codex real-account validation harness', () => {
|
|||
expect(env.CODEX_HOME).toBeUndefined()
|
||||
expect(env.ORCA_CODEX_HOME).toBeUndefined()
|
||||
expect(env.ZDOTDIR).toBeUndefined()
|
||||
expect(env.ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME).toBe('1')
|
||||
expect(env.SAFE_VALUE).toBe('preserved')
|
||||
})
|
||||
|
||||
it('pins the real-home flag off when the system-default lane is disabled', async () => {
|
||||
const primaryHome = path.join(os.tmpdir(), 'orca-primary-home-sentinel')
|
||||
const { layout, env } = runValidationModule<{
|
||||
layout: { tempRoot: string }
|
||||
env: Record<string, string | undefined>
|
||||
}>(
|
||||
`
|
||||
const { createValidationEnv, createValidationLayout } = await import(process.argv[1])
|
||||
const layout = await createValidationLayout({ primaryHome: process.argv[2] })
|
||||
const env = createValidationEnv({}, layout, { systemDefaultRealHome: 'off' })
|
||||
console.log(JSON.stringify({ layout, env }))
|
||||
`,
|
||||
[primaryHome]
|
||||
)
|
||||
cleanupPaths.push(layout.tempRoot)
|
||||
|
||||
expect(env.ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME).toBe('0')
|
||||
})
|
||||
|
||||
it('records only fingerprints for system-default and managed auth', async () => {
|
||||
const { layout, snapshot } = runValidationModule<{
|
||||
layout: { tempRoot: string }
|
||||
|
|
|
|||
|
|
@ -483,7 +483,6 @@ async function main() {
|
|||
HOME: isolatedHome,
|
||||
USERPROFILE: isolatedHome,
|
||||
ORCA_E2E_HOME_DIR: isolatedHome,
|
||||
ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME: '0',
|
||||
...(options.headful ? { ORCA_E2E_HEADFUL: '1' } : { ORCA_E2E_HEADLESS: '1' })
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ Object.assign(childEnv, {
|
|||
ORCA_DEV_USER_DATA_PATH: profileDir,
|
||||
HOME: isolatedHome,
|
||||
USERPROFILE: isolatedHome,
|
||||
ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME: '0',
|
||||
...(process.platform === 'linux'
|
||||
? { ELECTRON_DISABLE_SANDBOX: process.env.ELECTRON_DISABLE_SANDBOX ?? '1' }
|
||||
: {})
|
||||
|
|
|
|||
|
|
@ -219,7 +219,6 @@ async function runValidation(mode) {
|
|||
ORCA_DEV_USER_DATA_PATH: userDataPath,
|
||||
HOME: isolatedHome,
|
||||
USERPROFILE: isolatedHome,
|
||||
ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME: '0',
|
||||
ELECTRON_ENABLE_LOGGING: '1',
|
||||
ELECTRON_ENABLE_STACK_DUMPING: '1',
|
||||
ELECTRON_OZONE_PLATFORM_HINT: 'wayland',
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ export function launchDevApp({ cdpPort, userDataDir }) {
|
|||
ORCA_DEV_USER_DATA_PATH: userDataDir,
|
||||
HOME: isolatedHome,
|
||||
USERPROFILE: isolatedHome,
|
||||
ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME: '0',
|
||||
ORCA_SKIP_DEV_WEB_PREPARE: '1',
|
||||
ORCA_STARTUP_DIAGNOSTICS: '1',
|
||||
REMOTE_DEBUGGING_PORT: String(cdpPort),
|
||||
|
|
|
|||
|
|
@ -20,12 +20,7 @@ beforeEach(() => {
|
|||
testState.home = mkdtempSync(join(tmpdir(), 'orca-codex-status-home-'))
|
||||
// Why: the real-home check consults CODEX_HOME and the shell rc, so a
|
||||
// developer who exports one would otherwise fail this suite locally.
|
||||
for (const key of [
|
||||
'ORCA_USER_DATA_PATH',
|
||||
'ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME',
|
||||
'CODEX_HOME',
|
||||
'ORCA_CODEX_HOME'
|
||||
]) {
|
||||
for (const key of ['ORCA_USER_DATA_PATH', 'CODEX_HOME', 'ORCA_CODEX_HOME']) {
|
||||
previousEnv[key] = process.env[key]
|
||||
delete process.env[key]
|
||||
}
|
||||
|
|
@ -91,8 +86,8 @@ describe('CodexRuntimeHomeService.getMirroredHostHomePathForStatus', () => {
|
|||
expect(service.getMirroredHostHomePathForStatus()).toBe(account.managedHomePath)
|
||||
})
|
||||
|
||||
it('returns the shared runtime home when the real-home lane is off', async () => {
|
||||
process.env.ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME = '0'
|
||||
it('returns the shared runtime home when a custom CODEX_HOME keeps the mirror lane', async () => {
|
||||
process.env.CODEX_HOME = join(testState.home, 'custom-codex-home')
|
||||
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
||||
const { getOrcaManagedCodexHomePath } = await import('../codex/codex-home-paths')
|
||||
const service = new CodexRuntimeHomeService(createStore([], null) as never)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ beforeEach(() => {
|
|||
testState.home = mkdtempSync(join(tmpdir(), 'orca-codex-e-home-'))
|
||||
for (const key of [
|
||||
'ORCA_USER_DATA_PATH',
|
||||
'ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME',
|
||||
'ORCA_DISABLE_CODEX_TRUST_RPC',
|
||||
'CODEX_HOME',
|
||||
'ORCA_CODEX_HOME'
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -67,7 +67,6 @@ import {
|
|||
type CodexAccountSelectionTarget
|
||||
} from './runtime-selection'
|
||||
import { getDefaultWslDistro, getWslHome } from '../wsl'
|
||||
import { isCodexSystemDefaultRealHomeEnabled } from '../codex/codex-real-home-flag'
|
||||
import { hasCustomCodexHomeOverrideForLaunch } from '../codex/codex-real-home-path'
|
||||
import { invalidateCodexSessionBackfillMarker } from '../codex/codex-session-backfill-marker'
|
||||
import { assertOwnedHostCodexManagedHomePath } from './host-codex-managed-home-ownership'
|
||||
|
|
@ -176,8 +175,8 @@ export class CodexRuntimeHomeService {
|
|||
private readonly lastSyncedWslAccountIdByDistro = new Map<string, string | null>()
|
||||
private readonly wslRuntimeHomePathByDistro = new Map<string, string>()
|
||||
private skipNextReadBackForAccountId: string | null = null
|
||||
// Why: a flag-ON host account refreshes auth in its own home. Remember that
|
||||
// provenance so a later deselect/rollback never adopts stale shared bytes.
|
||||
// Why: a managed host account refreshes auth in its own home. Remember that
|
||||
// provenance so a later deselect never adopts stale shared bytes.
|
||||
private lastHostAccountUsedSelfContainedHome = false
|
||||
private sharedAuthRefreshBlockedByManagedTransition = false
|
||||
// Why: transient auth.json read/parse failures must not deselect an account.
|
||||
|
|
@ -236,7 +235,7 @@ export class CodexRuntimeHomeService {
|
|||
// system default without injecting a path Orca cannot prove it owns.
|
||||
}
|
||||
if (this.isHostSystemDefaultRealHome(launchEnv)) {
|
||||
// Why (flag ON, system default): run Codex on the user's own ~/.codex.
|
||||
// Why: the system default runs Codex on the user's own ~/.codex.
|
||||
// Returning null tells the PTY/env layer to inject no managed CODEX_HOME;
|
||||
// the retired mirror is refreshed only for pre-rollout PTYs.
|
||||
this.reconcileLegacySharedHomeForRetainedPanes()
|
||||
|
|
@ -254,17 +253,12 @@ export class CodexRuntimeHomeService {
|
|||
return this.getRuntimeHomePath()
|
||||
}
|
||||
|
||||
// Why: with the real-home flag ON, a managed HOST account runs against its own
|
||||
// self-contained CODEX_HOME (codex-accounts/<id>/home) instead of the shared
|
||||
// runtime mirror. Its auth.json lives there and codex refreshes it in place,
|
||||
// so two accounts never race one auth.json (GAP-5) and the mirror can be
|
||||
// deleted once no lane still injects it (GAP-1). WSL accounts keep their
|
||||
// per-distro lane; the flag-OFF opt-out keeps the shared-home hot-swap.
|
||||
// Why: a managed HOST account runs against its own self-contained CODEX_HOME
|
||||
// (codex-accounts/<id>/home) rather than the shared runtime mirror. Its
|
||||
// auth.json lives there and codex refreshes it in place, so two accounts never
|
||||
// race one auth.json. WSL accounts keep their per-distro lane.
|
||||
private getSelfContainedManagedHostAccount(): CodexManagedAccount | null {
|
||||
const settings = this.store.getSettings()
|
||||
if (!isCodexSystemDefaultRealHomeEnabled()) {
|
||||
return null
|
||||
}
|
||||
const account = this.getActiveAccount(
|
||||
settings.codexManagedAccounts,
|
||||
normalizeCodexRuntimeSelection(settings).host
|
||||
|
|
@ -276,22 +270,17 @@ export class CodexRuntimeHomeService {
|
|||
}
|
||||
|
||||
// Why: session discovery must surface a managed account's own rollouts wherever
|
||||
// they physically live. Flag ON makes every host managed home a live CODEX_HOME,
|
||||
// so scan them all. Flag OFF (opt-out/rollback) hands launches back to the shared
|
||||
// mirror, but a home that already accumulated rollouts while the flag was ON must
|
||||
// still surface them — otherwise opting out silently hides history that is safe on
|
||||
// disk. Gate the flag-OFF case on a sessions/ tree so a never-enabled install stays
|
||||
// byte-identical to today (its per-account homes hold only auth, no rollouts).
|
||||
// they physically live. Every host managed home is a live CODEX_HOME, so scan
|
||||
// them all.
|
||||
private getManagedHostAccountHomesForSessionDiscovery(): string[] {
|
||||
const settings = this.store.getSettings()
|
||||
const flagEnabled = isCodexSystemDefaultRealHomeEnabled()
|
||||
const homes: string[] = []
|
||||
for (const account of settings.codexManagedAccounts) {
|
||||
if (this.getWslManagedHomePath(account)) {
|
||||
continue
|
||||
}
|
||||
const trustedHome = this.getTrustedSelfContainedManagedHomePath(account)
|
||||
if (trustedHome && (flagEnabled || existsSync(join(trustedHome, 'sessions')))) {
|
||||
if (trustedHome) {
|
||||
homes.push(trustedHome)
|
||||
}
|
||||
}
|
||||
|
|
@ -420,8 +409,9 @@ export class CodexRuntimeHomeService {
|
|||
if (normalizeCodexRuntimeSelection(settings).host !== null) {
|
||||
return
|
||||
}
|
||||
const realHomeSelected = this.isHostSystemDefaultRealHomeSelected(launchEnv)
|
||||
if (realHomeSelected || !isCodexSystemDefaultRealHomeEnabled()) {
|
||||
// Why: reached only when the real-home lane is selected but its gate is off,
|
||||
// so the launch runs on the mirror and the backfill marker is stale.
|
||||
if (this.isHostSystemDefaultRealHomeSelected(launchEnv)) {
|
||||
invalidateCodexSessionBackfillMarker(
|
||||
join(getCodexSessionBackfillStateDirPath(), 'backfill-complete.json')
|
||||
)
|
||||
|
|
@ -463,10 +453,9 @@ export class CodexRuntimeHomeService {
|
|||
// mirror, so include the real root for both directly-routed host lanes.
|
||||
homes.push(getSystemCodexHomePath())
|
||||
}
|
||||
// Why: flag ON routes each managed host account to its own self-contained
|
||||
// home, so its rollouts live there rather than in the shared mirror. Scan
|
||||
// every such home — plus any that retained rollouts across an opt-out — so
|
||||
// account-scoped sessions still surface in the AI Vault.
|
||||
// Why: each managed host account runs in its own self-contained home, so
|
||||
// its rollouts live there rather than in the shared mirror. Scan every such
|
||||
// home so account-scoped sessions still surface in the AI Vault.
|
||||
for (const perAccountHome of this.getManagedHostAccountHomesForSessionDiscovery()) {
|
||||
homes.push(perAccountHome)
|
||||
}
|
||||
|
|
@ -475,9 +464,8 @@ export class CodexRuntimeHomeService {
|
|||
|
||||
/**
|
||||
* The account-owned CODEX_HOME the current HOST selection runs against, or
|
||||
* null when the selection is not routed to one (system default, or the
|
||||
* flag-OFF shared mirror, which every account hot-swaps and so names no
|
||||
* account).
|
||||
* null when the selection is not routed to one (system default, or a WSL
|
||||
* account, whose home lives inside the distro).
|
||||
*
|
||||
* Read-only on purpose: session discovery ranks homes with this before any
|
||||
* launch prep, so it must create no directories and sync no auth.
|
||||
|
|
@ -505,13 +493,13 @@ export class CodexRuntimeHomeService {
|
|||
this.realHomeLaneGate = gate
|
||||
}
|
||||
|
||||
// Why: real-home routing applies only to the host system-default selection
|
||||
// with the staged flag ON. Managed accounts keep hot-swap isolation; custom
|
||||
// CODEX_HOMEs stay managed until phase 1 can track cleanup across old homes.
|
||||
// Why: real-home routing applies only to the host system-default selection.
|
||||
// Managed accounts run in their own homes; Windows (no shell-startup probe)
|
||||
// and custom CODEX_HOMEs stay on the mirror until cleanup can be tracked
|
||||
// across old homes.
|
||||
isHostSystemDefaultRealHomeSelected(launchEnv?: NodeJS.ProcessEnv): boolean {
|
||||
const settings = this.store.getSettings()
|
||||
if (
|
||||
!isCodexSystemDefaultRealHomeEnabled() ||
|
||||
normalizeCodexRuntimeSelection(settings).host !== null ||
|
||||
!isShellStartupEnvProbeSupported()
|
||||
) {
|
||||
|
|
@ -640,9 +628,9 @@ export class CodexRuntimeHomeService {
|
|||
}
|
||||
const settings = this.store.getSettings()
|
||||
if (this.lastHostAccountUsedSelfContainedHome) {
|
||||
// Why: E auth is already canonical in the per-account home. Reset the
|
||||
// legacy mirror baseline without reading it; flag-OFF can then seed the
|
||||
// mirror from canonical storage, while real-home deselect needs no sync.
|
||||
// Why: the account's auth is already canonical in its own home. Reset the
|
||||
// legacy mirror baseline without reading it; a real-home deselect needs no
|
||||
// further sync, and the mirror lane below re-seeds from canonical storage.
|
||||
this.lastHostAccountUsedSelfContainedHome = false
|
||||
this.lastSyncedAccountId = null
|
||||
this.lastWrittenAuthJson = null
|
||||
|
|
@ -668,105 +656,16 @@ export class CodexRuntimeHomeService {
|
|||
settings.codexManagedAccounts,
|
||||
normalizeCodexRuntimeSelection(settings).host
|
||||
)
|
||||
const previousAccount = this.getActiveAccount(
|
||||
settings.codexManagedAccounts,
|
||||
this.lastSyncedAccountId
|
||||
)
|
||||
if (this.getWslManagedHomePath(activeAccount)) {
|
||||
const previousWasHostManaged = previousAccount && !this.getWslManagedHomePath(previousAccount)
|
||||
const outgoingReadBackResult = previousWasHostManaged
|
||||
? this.readBackRefreshedTokensForAccount(previousAccount, {
|
||||
updateLastWrittenAuthJson: false
|
||||
})
|
||||
: 'unchanged'
|
||||
if (previousWasHostManaged) {
|
||||
this.restoreSystemDefaultSnapshot({
|
||||
detectExternalLogin: outgoingReadBackResult !== 'rejected'
|
||||
})
|
||||
}
|
||||
if (activeAccount) {
|
||||
// Why: only a WSL-managed account can reach here — every host account was
|
||||
// routed to its own self-contained home above. Its auth lives in the
|
||||
// distro-local runtime home, so the host mirror only drops its baseline.
|
||||
this.lastSyncedAccountId = null
|
||||
this.lastWrittenAuthJson = null
|
||||
this.skipNextReadBackForAccountId = null
|
||||
return
|
||||
}
|
||||
let outgoingReadBackResult: CodexReadBackResult = 'unchanged'
|
||||
if (previousAccount && previousAccount.id !== activeAccount?.id) {
|
||||
outgoingReadBackResult = this.readBackRefreshedTokensForAccount(previousAccount, {
|
||||
updateLastWrittenAuthJson: true
|
||||
})
|
||||
}
|
||||
if (!activeAccount) {
|
||||
if (normalizeCodexRuntimeSelection(settings).host) {
|
||||
this.store.updateSettings({
|
||||
activeCodexManagedAccountId: null,
|
||||
activeCodexManagedAccountIdsByRuntime: {
|
||||
...normalizeCodexRuntimeSelection(settings),
|
||||
host: null
|
||||
}
|
||||
})
|
||||
}
|
||||
// Why: only restore the system-default mirror when leaving a managed account; otherwise later syncs mirror current ~/.codex instead of replaying an old snapshot.
|
||||
if (this.lastSyncedAccountId !== null) {
|
||||
this.restoreSystemDefaultSnapshot({
|
||||
detectExternalLogin: outgoingReadBackResult !== 'rejected'
|
||||
})
|
||||
this.lastSyncedAccountId = null
|
||||
} else if (!runtimeAuthExistedBeforeSync) {
|
||||
const logoutMarkerStatus = this.getRuntimeLogoutMarkerStatus()
|
||||
if (logoutMarkerStatus.kind === 'applies') {
|
||||
this.lastWrittenAuthJson = null
|
||||
} else if (
|
||||
logoutMarkerStatus.kind === 'system-default-changed' &&
|
||||
logoutMarkerStatus.systemDefaultAuthJson !== null
|
||||
) {
|
||||
this.restoreSystemDefaultSnapshot({ detectExternalLogin: false })
|
||||
} else if (logoutMarkerStatus.kind === 'system-default-changed') {
|
||||
// Why: a real ~/.codex logout after a local runtime logout should keep runtime auth absent, not restore the stale snapshot.
|
||||
this.captureSystemDefaultSnapshot({ force: true })
|
||||
this.persistRuntimeLogoutMarker(null)
|
||||
this.lastWrittenAuthJson = null
|
||||
} else if (this.lastWrittenAuthJson === null) {
|
||||
// Why: unmanaged sessions use an Orca-owned CODEX_HOME; seed it once from system-default auth so terminals stay logged in without mutating ~/.codex.
|
||||
this.restoreSystemDefaultSnapshot({ detectExternalLogin: false })
|
||||
} else {
|
||||
this.persistRuntimeLogoutMarker()
|
||||
}
|
||||
} else {
|
||||
this.clearRuntimeLogoutMarker()
|
||||
this.syncRuntimeAuthWithSystemDefault()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const activeAuthPath = join(activeAccount.managedHomePath, 'auth.json')
|
||||
const authAbsence = this.credentialAbsenceGrace.assess(activeAuthPath)
|
||||
if (authAbsence.state !== 'present' && authAbsence.state !== 'incomplete') {
|
||||
if (!authAbsence.durable) {
|
||||
if (this.sharedRuntimeAuthBelongsToAccount(activeAccount)) {
|
||||
// Why: mid-rotation reads look missing/unreadable for a moment; skip
|
||||
// this sync without deselecting and let a settled read decide later.
|
||||
console.warn(
|
||||
'[codex-runtime-home] Active managed account auth.json unavailable, keeping selection through grace window'
|
||||
)
|
||||
return
|
||||
}
|
||||
// Why: the runtime home still holds another account, so riding out the
|
||||
// grace would launch that account under this selection. Not being able
|
||||
// to read the selected account is no license to run a different one.
|
||||
console.warn(
|
||||
'[codex-runtime-home] Active managed account auth.json unavailable while the runtime home holds another account, clearing runtime auth'
|
||||
)
|
||||
this.clearRuntimeAuthForUnprovenSelection()
|
||||
return
|
||||
}
|
||||
console.warn(
|
||||
'[codex-runtime-home] Active managed account credential is unavailable, restoring system default'
|
||||
)
|
||||
// Why: valid credential-free JSON is an explicit logout; never revive it
|
||||
// from stale shared-home bytes while clearing the selection.
|
||||
if (authAbsence.state !== 'no-credential' && this.lastSyncedAccountId === activeAccount.id) {
|
||||
outgoingReadBackResult = this.recoverRefreshForMissingActiveAccount(activeAccount)
|
||||
}
|
||||
if (normalizeCodexRuntimeSelection(settings).host) {
|
||||
this.store.updateSettings({
|
||||
activeCodexManagedAccountId: null,
|
||||
activeCodexManagedAccountIdsByRuntime: {
|
||||
|
|
@ -774,38 +673,35 @@ export class CodexRuntimeHomeService {
|
|||
host: null
|
||||
}
|
||||
})
|
||||
if (this.lastSyncedAccountId !== null) {
|
||||
this.restoreSystemDefaultSnapshot({
|
||||
detectExternalLogin: outgoingReadBackResult !== 'rejected'
|
||||
})
|
||||
this.lastSyncedAccountId = null
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (this.lastSyncedAccountId === null) {
|
||||
this.captureSystemDefaultSnapshot({ force: true })
|
||||
}
|
||||
|
||||
// Why: Codex refreshes OAuth tokens in the runtime auth.json; if it differs from Orca's last write, read those back to managed storage before overwriting.
|
||||
if (this.lastSyncedAccountId === activeAccount.id) {
|
||||
if (this.skipNextReadBackForAccountId === activeAccount.id) {
|
||||
this.skipNextReadBackForAccountId = null
|
||||
// Why: only restore the system-default mirror when leaving a managed account; otherwise later syncs mirror current ~/.codex instead of replaying an old snapshot.
|
||||
if (this.lastSyncedAccountId !== null) {
|
||||
this.restoreSystemDefaultSnapshot({ detectExternalLogin: true })
|
||||
this.lastSyncedAccountId = null
|
||||
} else if (!runtimeAuthExistedBeforeSync) {
|
||||
const logoutMarkerStatus = this.getRuntimeLogoutMarkerStatus()
|
||||
if (logoutMarkerStatus.kind === 'applies') {
|
||||
this.lastWrittenAuthJson = null
|
||||
} else if (
|
||||
logoutMarkerStatus.kind === 'system-default-changed' &&
|
||||
logoutMarkerStatus.systemDefaultAuthJson !== null
|
||||
) {
|
||||
this.restoreSystemDefaultSnapshot({ detectExternalLogin: false })
|
||||
} else if (logoutMarkerStatus.kind === 'system-default-changed') {
|
||||
// Why: a real ~/.codex logout after a local runtime logout should keep runtime auth absent, not restore the stale snapshot.
|
||||
this.captureSystemDefaultSnapshot({ force: true })
|
||||
this.persistRuntimeLogoutMarker(null)
|
||||
this.lastWrittenAuthJson = null
|
||||
} else if (this.lastWrittenAuthJson === null) {
|
||||
// Why: unmanaged sessions use an Orca-owned CODEX_HOME; seed it once from system-default auth so terminals stay logged in without mutating ~/.codex.
|
||||
this.restoreSystemDefaultSnapshot({ detectExternalLogin: false })
|
||||
} else {
|
||||
this.readBackRefreshedTokens({
|
||||
updateLastWrittenAuthJson: true
|
||||
})
|
||||
this.persistRuntimeLogoutMarker()
|
||||
}
|
||||
} else {
|
||||
this.clearRuntimeLogoutMarker()
|
||||
this.syncRuntimeAuthWithSystemDefault()
|
||||
}
|
||||
|
||||
if (this.lastSyncedAccountId !== activeAccount.id) {
|
||||
this.skipNextReadBackForAccountId = null
|
||||
}
|
||||
this.lastSyncedAccountId = activeAccount.id
|
||||
this.writeRuntimeAuth(readFileSync(activeAuthPath, 'utf-8'), {
|
||||
owner: 'managed',
|
||||
accountId: activeAccount.id
|
||||
})
|
||||
}
|
||||
|
||||
// Why: re-auth/add-account write fresh managed tokens, so skip the next read-back to avoid clobbering them with stale runtime tokens.
|
||||
|
|
@ -818,26 +714,6 @@ export class CodexRuntimeHomeService {
|
|||
this.skipNextReadBackForAccountId = accountId
|
||||
}
|
||||
|
||||
private readBackRefreshedTokens(options: {
|
||||
updateLastWrittenAuthJson: boolean
|
||||
}): CodexReadBackResult {
|
||||
const selectedAccountId = normalizeCodexRuntimeSelection(this.store.getSettings()).host
|
||||
if (selectedAccountId) {
|
||||
const selectedAccountResult = this.readBackRefreshedTokensFromPath(
|
||||
this.getRuntimeAuthPath(),
|
||||
{
|
||||
...options,
|
||||
expectedAccountId: selectedAccountId
|
||||
}
|
||||
)
|
||||
if (selectedAccountResult !== 'rejected') {
|
||||
return selectedAccountResult
|
||||
}
|
||||
}
|
||||
|
||||
return this.readBackRefreshedTokensFromPath(this.getRuntimeAuthPath(), options)
|
||||
}
|
||||
|
||||
private readBackRefreshedTokensFromPath(
|
||||
runtimeAuthPath: string,
|
||||
options: {
|
||||
|
|
@ -895,119 +771,6 @@ export class CodexRuntimeHomeService {
|
|||
}
|
||||
}
|
||||
|
||||
private readBackRefreshedTokensForAccount(
|
||||
account: CodexManagedAccount,
|
||||
options: { updateLastWrittenAuthJson: boolean }
|
||||
): CodexReadBackResult {
|
||||
return this.readBackRefreshedTokensFromPath(this.getRuntimeAuthPath(), {
|
||||
...options,
|
||||
expectedAccountId: account.id
|
||||
})
|
||||
}
|
||||
|
||||
private recoverRefreshForMissingActiveAccount(account: CodexManagedAccount): CodexReadBackResult {
|
||||
try {
|
||||
const runtimeAuthPath = this.getRuntimeAuthPath()
|
||||
if (!existsSync(runtimeAuthPath) || this.lastWrittenAuthJson === null) {
|
||||
return 'rejected'
|
||||
}
|
||||
const runtimeContents = readFileSync(runtimeAuthPath, 'utf-8')
|
||||
if (runtimeContents === this.lastWrittenAuthJson) {
|
||||
return 'unchanged'
|
||||
}
|
||||
// Why: the canonical file is gone, so the exact in-memory bytes Orca
|
||||
// previously mirrored are the only safe identity baseline for recovery.
|
||||
if (!codexAuthMatchesManagedAccount(runtimeContents, account, this.lastWrittenAuthJson)) {
|
||||
return 'rejected'
|
||||
}
|
||||
writeFileAtomically(join(account.managedHomePath, 'auth.json'), runtimeContents, {
|
||||
mode: 0o600
|
||||
})
|
||||
this.lastWrittenAuthJson = runtimeContents
|
||||
return 'persisted'
|
||||
} catch (error) {
|
||||
console.warn('[codex-runtime-home] Failed to recover missing managed auth:', error)
|
||||
return 'rejected'
|
||||
}
|
||||
}
|
||||
|
||||
// Why: stale panes can overwrite the mirror after provenance is committed, so
|
||||
// launch ownership needs current-byte identity or Orca's exact same-run write.
|
||||
private sharedRuntimeAuthBelongsToAccount(account: CodexManagedAccount): boolean {
|
||||
if (!existsSync(this.getRuntimeAuthPath())) {
|
||||
return true
|
||||
}
|
||||
const runtimeAuth = this.readRuntimeAuthForProvenance()
|
||||
if (runtimeAuth !== null) {
|
||||
if (codexAuthMatchesManagedAccount(runtimeAuth, account, null)) {
|
||||
return true
|
||||
}
|
||||
// Why: Orca itself mirrored these exact bytes for this account this run.
|
||||
if (this.lastSyncedAccountId === account.id && this.lastWrittenAuthJson === runtimeAuth) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Why: the absence may still heal, so keep the selection — but leave no other
|
||||
// identity's credentials behind for the launch. Logged out beats logged in as
|
||||
// someone else, and fencing stops later syncs adopting the removed bytes.
|
||||
private clearRuntimeAuthForUnprovenSelection(): void {
|
||||
const runtimeAuthPath = this.getRuntimeAuthPath()
|
||||
try {
|
||||
// Why: a refresh Codex wrote into the mirror belongs to whoever owns
|
||||
// those bytes; persist it to that owner's home — managed or ~/.codex —
|
||||
// first, then fence so later syncs cannot adopt the removed bytes.
|
||||
const readBackResult = this.readBackRefreshedTokensFromPath(runtimeAuthPath, {
|
||||
updateLastWrittenAuthJson: false
|
||||
})
|
||||
if (readBackResult === 'rejected') {
|
||||
this.readBackRefreshedSystemDefaultAuth()
|
||||
}
|
||||
this.persistSharedRuntimeAuthProvenance({ owner: 'fenced' })
|
||||
} catch (error) {
|
||||
// Why: rescue and metadata are best-effort; neither may leave another
|
||||
// identity's credentials in the home returned to the launch.
|
||||
console.warn('[codex-runtime-home] Failed to rescue or fence unproven runtime auth:', error)
|
||||
}
|
||||
this.lastWrittenAuthJson = null
|
||||
try {
|
||||
rmSync(runtimeAuthPath, { force: true })
|
||||
} catch (error) {
|
||||
// Why: a fence is Orca metadata that Codex does not read. If Windows or
|
||||
// another host keeps auth.json locked, failing launch is the safe result.
|
||||
throw new Error('Cannot safely launch Codex while stale runtime auth remains.', {
|
||||
cause: error
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Why: a token Codex refreshed inside the mirror has no managed home to fall
|
||||
// back to when the mirror holds the user's own ~/.codex credential, so persist
|
||||
// it there before the unproven mirror is dropped.
|
||||
private readBackRefreshedSystemDefaultAuth(): void {
|
||||
const runtimeAuth = this.readRuntimeAuthForProvenance()
|
||||
const systemDefaultAuth = this.readSystemDefaultAuth()
|
||||
if (runtimeAuth === null || systemDefaultAuth === null || runtimeAuth === systemDefaultAuth) {
|
||||
return
|
||||
}
|
||||
const claim = this.resolveSystemDefaultMirrorClaim(
|
||||
runtimeAuth,
|
||||
this.resolveSharedRuntimeAuthProvenanceStatus()
|
||||
)
|
||||
if (
|
||||
!claim.ownershipProven ||
|
||||
claim.mirroredAuthJson === null ||
|
||||
systemDefaultAuth !== claim.mirroredAuthJson ||
|
||||
!this.runtimeAuthMatchesSystemDefaultIdentity(runtimeAuth, systemDefaultAuth)
|
||||
) {
|
||||
return
|
||||
}
|
||||
this.writeSystemDefaultAuth(runtimeAuth)
|
||||
this.captureSystemDefaultSnapshot({ force: true })
|
||||
}
|
||||
|
||||
// Why: which ~/.codex bytes the mirror was seeded from, and whether the system
|
||||
// default can be proven to own the mirror at all.
|
||||
private resolveSystemDefaultMirrorClaim(
|
||||
|
|
@ -1399,9 +1162,6 @@ export class CodexRuntimeHomeService {
|
|||
|
||||
private safeMigrateLegacySharedAuth(): void {
|
||||
const settings = this.store.getSettings()
|
||||
if (!isCodexSystemDefaultRealHomeEnabled()) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
migrateLegacySharedAuthToPerAccountHome({
|
||||
activeHostAccountId: normalizeCodexRuntimeSelection(settings).host,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import type { GlobalSettings } from '../../shared/types'
|
|||
import { CodexRuntimeHomeService } from './runtime-home-service'
|
||||
|
||||
const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform')
|
||||
const originalRealHomeOverride = process.env.ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME
|
||||
const originalCodexHome = process.env.CODEX_HOME
|
||||
const originalOrcaCodexHome = process.env.ORCA_CODEX_HOME
|
||||
|
||||
|
|
@ -11,7 +10,6 @@ afterEach(() => {
|
|||
if (originalPlatform) {
|
||||
Object.defineProperty(process, 'platform', originalPlatform)
|
||||
}
|
||||
restoreEnv('ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME', originalRealHomeOverride)
|
||||
restoreEnv('CODEX_HOME', originalCodexHome)
|
||||
restoreEnv('ORCA_CODEX_HOME', originalOrcaCodexHome)
|
||||
})
|
||||
|
|
@ -19,7 +17,6 @@ afterEach(() => {
|
|||
describe('Windows System Default Codex home ownership', () => {
|
||||
it('stays managed when PowerShell profile state cannot be inspected', () => {
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' })
|
||||
process.env.ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME = '1'
|
||||
delete process.env.CODEX_HOME
|
||||
delete process.env.ORCA_CODEX_HOME
|
||||
|
||||
|
|
|
|||
|
|
@ -46,33 +46,10 @@ function decodeEncodedWslBashCommand(command: string): string {
|
|||
return encoded ? Buffer.from(encoded, 'base64').toString('utf8') : command
|
||||
}
|
||||
|
||||
// Why: the shipped code no longer reads a settings flag — the legacy mirror
|
||||
// lane is reachable only through the test-rig env override. Route the old
|
||||
// per-test override key to that env var so lane coverage keeps working.
|
||||
type TestSettingsOverrides = Partial<GlobalSettings> & {
|
||||
codexSystemDefaultRealHomeEnabled?: boolean
|
||||
}
|
||||
|
||||
function setRealHomeLaneForTest(enabled: boolean): void {
|
||||
process.env.ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME = enabled ? '1' : '0'
|
||||
}
|
||||
|
||||
const initialRealHomeLaneEnv = process.env.ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME
|
||||
afterEach(() => {
|
||||
if (initialRealHomeLaneEnv === undefined) {
|
||||
delete process.env.ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME
|
||||
} else {
|
||||
process.env.ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME = initialRealHomeLaneEnv
|
||||
}
|
||||
})
|
||||
|
||||
function createSettings(overrides: TestSettingsOverrides = {}): GlobalSettings {
|
||||
function createSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings {
|
||||
const appFontFamily = overrides.appFontFamily ?? 'Geist'
|
||||
const agentStatusHooksEnabled = overrides.agentStatusHooksEnabled ?? true
|
||||
const tabAutoGenerateTitle = overrides.tabAutoGenerateTitle ?? false
|
||||
// Config-sync/hot-swap tests assert the shared-mirror path; production is
|
||||
// real-home always, so opt these managed cases out unless a test overrides it.
|
||||
setRealHomeLaneForTest(overrides.codexSystemDefaultRealHomeEnabled ?? false)
|
||||
return {
|
||||
workspaceDir: testState.fakeHomeDir,
|
||||
nestWorkspaces: false,
|
||||
|
|
@ -430,7 +407,6 @@ describe('CodexAccountService config sync', () => {
|
|||
'approval_policy = "on-request"\n'
|
||||
)
|
||||
const settings = createSettings({
|
||||
codexSystemDefaultRealHomeEnabled: true,
|
||||
codexManagedAccounts: [
|
||||
{
|
||||
id: 'account-1',
|
||||
|
|
@ -476,43 +452,6 @@ describe('CodexAccountService config sync', () => {
|
|||
expectSanitizedManagedConfig()
|
||||
})
|
||||
|
||||
it('keeps flag-off config mirroring byte-identical', async () => {
|
||||
const fixture = await createCanonicalHookTrustFixture()
|
||||
const canonicalConfigPath = join(testState.fakeHomeDir, '.codex', 'config.toml')
|
||||
writeFileSync(canonicalConfigPath, fixture.config, 'utf-8')
|
||||
const managedHomePath = createManagedHome(
|
||||
testState.userDataDir,
|
||||
'account-1',
|
||||
'approval_policy = "on-request"\n'
|
||||
)
|
||||
const settings = createSettings({
|
||||
codexSystemDefaultRealHomeEnabled: false,
|
||||
codexManagedAccounts: [
|
||||
{
|
||||
id: 'account-1',
|
||||
email: 'user@example.com',
|
||||
managedHomePath,
|
||||
providerAccountId: null,
|
||||
workspaceLabel: null,
|
||||
workspaceAccountId: null,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
lastAuthenticatedAt: 1
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const { CodexAccountService } = await import('./service')
|
||||
new CodexAccountService(
|
||||
createStore(settings) as never,
|
||||
createRateLimits() as never,
|
||||
createRuntimeHome() as never
|
||||
)
|
||||
|
||||
expect(readFileSync(join(managedHomePath, 'config.toml'), 'utf-8')).toBe(fixture.config)
|
||||
expect(readFileSync(canonicalConfigPath, 'utf-8')).toBe(fixture.config)
|
||||
})
|
||||
|
||||
it('rewrites relative path config values when syncing into managed homes', async () => {
|
||||
const canonicalConfigPath = join(testState.fakeHomeDir, '.codex', 'config.toml')
|
||||
writeFileSync(
|
||||
|
|
@ -556,7 +495,7 @@ describe('CodexAccountService config sync', () => {
|
|||
expect(managedConfig).toContain('sandbox_mode = "danger-full-access"')
|
||||
})
|
||||
|
||||
it('does not rewrite managed configs that already match canonical config', async () => {
|
||||
it('does not rewrite a managed config the previous mirror pass already settled', async () => {
|
||||
const canonicalConfigPath = join(testState.fakeHomeDir, '.codex', 'config.toml')
|
||||
const { escapeTomlString } = await import('../codex/config-toml-trust')
|
||||
const userHookKey = `${join(testState.fakeHomeDir, '.codex', 'user-hooks.json')}:stop:0:0`
|
||||
|
|
@ -575,8 +514,6 @@ describe('CodexAccountService config sync', () => {
|
|||
'{"account":"managed"}\n'
|
||||
)
|
||||
const managedConfigPath = join(managedHomePath, 'config.toml')
|
||||
const oldDate = new Date('2024-01-01T00:00:00.000Z')
|
||||
utimesSync(managedConfigPath, oldDate, oldDate)
|
||||
const settings = createSettings({
|
||||
codexManagedAccounts: [
|
||||
{
|
||||
|
|
@ -600,6 +537,15 @@ describe('CodexAccountService config sync', () => {
|
|||
const { CodexAccountService } = await import('./service')
|
||||
new CodexAccountService(store as never, rateLimits as never, runtimeHome as never)
|
||||
|
||||
// The first pass remaps the user hook-trust entry into this home; once that
|
||||
// has settled, a later pass must leave the file completely untouched.
|
||||
const settledConfig = readFileSync(managedConfigPath, 'utf-8')
|
||||
const oldDate = new Date('2024-01-01T00:00:00.000Z')
|
||||
utimesSync(managedConfigPath, oldDate, oldDate)
|
||||
|
||||
new CodexAccountService(store as never, rateLimits as never, runtimeHome as never)
|
||||
|
||||
expect(readFileSync(managedConfigPath, 'utf-8')).toBe(settledConfig)
|
||||
expect(statSync(managedConfigPath).mtimeMs).toBeLessThan(Date.now() - 60_000)
|
||||
})
|
||||
|
||||
|
|
@ -659,8 +605,7 @@ describe('CodexAccountService config sync', () => {
|
|||
|
||||
it('re-syncs config when selecting an account', async () => {
|
||||
const canonicalConfigPath = join(testState.fakeHomeDir, '.codex', 'config.toml')
|
||||
const canonicalConfig = 'approval_policy = "never"\nsandbox_mode = "danger-full-access"\n'
|
||||
writeFileSync(canonicalConfigPath, canonicalConfig, 'utf-8')
|
||||
writeFileSync(canonicalConfigPath, 'sandbox_mode = "danger-full-access"\n', 'utf-8')
|
||||
const managedHomePath = createManagedHome(
|
||||
testState.userDataDir,
|
||||
'account-1',
|
||||
|
|
@ -696,7 +641,11 @@ describe('CodexAccountService config sync', () => {
|
|||
|
||||
await service.selectAccount('account-1')
|
||||
|
||||
expect(readFileSync(join(managedHomePath, 'config.toml'), 'utf-8')).toBe(canonicalConfig)
|
||||
// Selecting merges canonical settings into the account's own home rather
|
||||
// than overwriting it, so its local approval_policy survives the re-sync.
|
||||
expect(readFileSync(join(managedHomePath, 'config.toml'), 'utf-8')).toBe(
|
||||
'sandbox_mode = "danger-full-access"\napproval_policy = "untrusted"\n'
|
||||
)
|
||||
expect(rateLimits.refreshForCodexAccountChange).toHaveBeenCalledTimes(1)
|
||||
expect(runtimeHome.syncForCurrentSelection).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
|
@ -855,7 +804,7 @@ describe('CodexAccountService config sync', () => {
|
|||
readHookTrustEntries = (await import('../codex/config-toml-trust')).readHookTrustEntries
|
||||
writeFileSync(join(testState.fakeHomeDir, '.codex', 'config.toml'), fixture.config, 'utf-8')
|
||||
|
||||
const store = createStore(createSettings({ codexSystemDefaultRealHomeEnabled: true }))
|
||||
const store = createStore(createSettings())
|
||||
const rateLimits = createRateLimits()
|
||||
const runtimeHome = createRuntimeHome()
|
||||
const { CodexAccountService } = await import('./service')
|
||||
|
|
@ -1842,7 +1791,7 @@ describe('CodexAccountService config sync', () => {
|
|||
expect(onHostSystemDefaultSelected).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('selectAccount immediately rewrites the shared runtime auth for existing terminals', async () => {
|
||||
it('selectAccount switches managed accounts without routing auth through the shared mirror', async () => {
|
||||
const firstAuth = createCodexAuthJson('one@example.com', 'acct-one', 'one')
|
||||
const secondAuth = createCodexAuthJson('two@example.com', 'acct-two', 'two')
|
||||
const firstManagedHomePath = createManagedHome(
|
||||
|
|
@ -1890,7 +1839,7 @@ describe('CodexAccountService config sync', () => {
|
|||
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
||||
const runtimeHome = new CodexRuntimeHomeService(store as never)
|
||||
const runtimeAuthPath = join(testState.userDataDir, 'codex-runtime-home', 'home', 'auth.json')
|
||||
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(firstAuth)
|
||||
expect(existsSync(runtimeAuthPath)).toBe(false)
|
||||
|
||||
const { CodexAccountService } = await import('./service')
|
||||
const service = new CodexAccountService(
|
||||
|
|
@ -1901,7 +1850,11 @@ describe('CodexAccountService config sync', () => {
|
|||
|
||||
await service.selectAccount('account-2')
|
||||
|
||||
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(secondAuth)
|
||||
// Each managed host account launches against its own home, so a switch must
|
||||
// leave both credential files alone and never copy either into the mirror.
|
||||
expect(existsSync(runtimeAuthPath)).toBe(false)
|
||||
expect(readFileSync(join(firstManagedHomePath, 'auth.json'), 'utf-8')).toBe(firstAuth)
|
||||
expect(readFileSync(join(secondManagedHomePath, 'auth.json'), 'utf-8')).toBe(secondAuth)
|
||||
expect(existsSync(join(testState.userDataDir, 'codex-runtime-home', 'launch'))).toBe(false)
|
||||
expect(existsSync(join(testState.userDataDir, 'codex-runtime-home', 'active'))).toBe(false)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import type { CodexRuntimeHomeService } from './runtime-home-service'
|
|||
import { writeFileAtomically } from './fs-utils'
|
||||
import { rewriteRelativePathConfigValues } from '../codex/codex-config-path-reference-rewrite'
|
||||
import { stripCodexManagedHookTrustEntriesFromConfig } from '../codex/codex-managed-trust-reconciliation'
|
||||
import { isCodexSystemDefaultRealHomeEnabled } from '../codex/codex-real-home-flag'
|
||||
import { getCodexManagedHookInstallMaterial } from '../codex/hook-service'
|
||||
import { syncSystemConfigIntoManagedCodexHome } from '../codex/codex-config-mirror'
|
||||
import { getSystemCodexHomePath } from '../codex/codex-home-paths'
|
||||
|
|
@ -1238,9 +1237,9 @@ export class CodexAccountService {
|
|||
}
|
||||
|
||||
private isSelfContainedHostManagedHome(managedHomePath: string): boolean {
|
||||
// Why: flag ON makes each host account home its own launch CODEX_HOME. WSL
|
||||
// homes keep their distro-local seed lane; the flag-OFF opt-out is unchanged.
|
||||
return isCodexSystemDefaultRealHomeEnabled() && !parseWslUncPath(managedHomePath)
|
||||
// Why: each host account home is its own launch CODEX_HOME. WSL homes keep
|
||||
// their distro-local seed lane.
|
||||
return !parseWslUncPath(managedHomePath)
|
||||
}
|
||||
|
||||
private syncCanonicalConfigIntoManagedHome(
|
||||
|
|
@ -1270,18 +1269,15 @@ export class CodexAccountService {
|
|||
// account while preserving consistent Codex behavior. Managed homes are
|
||||
// real CODEX_HOMEs for `codex login`, so relative path-valued settings
|
||||
// must keep resolving against the home the config was read from.
|
||||
let sanitizedConfig = canonicalConfig.contents
|
||||
if (isCodexSystemDefaultRealHomeEnabled()) {
|
||||
const material = getCodexManagedHookInstallMaterial()
|
||||
// Why: source-home Orca trust is foreign to each managed home's hooks.json.
|
||||
sanitizedConfig = stripCodexManagedHookTrustEntriesFromConfig(canonicalConfig.contents, {
|
||||
runtimeHomePath: canonicalConfig.sourceHomePath,
|
||||
sourcePath: canonicalConfig.sourceHooksPath,
|
||||
command: material.command,
|
||||
managedEventLabels: new Set(Object.values(material.eventLabel)),
|
||||
timeoutSec: MANAGED_HOOK_TIMEOUT_SECONDS
|
||||
})
|
||||
}
|
||||
const material = getCodexManagedHookInstallMaterial()
|
||||
// Why: source-home Orca trust is foreign to each managed home's hooks.json.
|
||||
const sanitizedConfig = stripCodexManagedHookTrustEntriesFromConfig(canonicalConfig.contents, {
|
||||
runtimeHomePath: canonicalConfig.sourceHomePath,
|
||||
sourcePath: canonicalConfig.sourceHooksPath,
|
||||
command: material.command,
|
||||
managedEventLabels: new Set(Object.values(material.eventLabel)),
|
||||
timeoutSec: MANAGED_HOOK_TIMEOUT_SECONDS
|
||||
})
|
||||
this.writeManagedConfig(
|
||||
trustedManagedHomePath,
|
||||
rewriteRelativePathConfigValues(sanitizedConfig, canonicalConfig.sourceHomePath)
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { isCodexSystemDefaultRealHomeEnabled } from './codex-real-home-flag'
|
||||
|
||||
const ENV_FLAG = 'ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME'
|
||||
let previousEnvFlag: string | undefined
|
||||
|
||||
beforeEach(() => {
|
||||
previousEnvFlag = process.env[ENV_FLAG]
|
||||
delete process.env[ENV_FLAG]
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (previousEnvFlag === undefined) {
|
||||
delete process.env[ENV_FLAG]
|
||||
} else {
|
||||
process.env[ENV_FLAG] = previousEnvFlag
|
||||
}
|
||||
})
|
||||
|
||||
describe('isCodexSystemDefaultRealHomeEnabled', () => {
|
||||
it('is unconditionally ON in production (no settings consulted)', () => {
|
||||
expect(isCodexSystemDefaultRealHomeEnabled()).toBe(true)
|
||||
})
|
||||
|
||||
it('lets the test-rig env override force ON explicitly', () => {
|
||||
for (const raw of ['1', 'true', 'on', 'TRUE', ' On ']) {
|
||||
process.env[ENV_FLAG] = raw
|
||||
expect(isCodexSystemDefaultRealHomeEnabled()).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('lets the test-rig env override pin the legacy managed lane OFF', () => {
|
||||
for (const raw of ['0', 'false', 'off']) {
|
||||
process.env[ENV_FLAG] = raw
|
||||
expect(isCodexSystemDefaultRealHomeEnabled()).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('ignores an unrecognized env value and stays ON', () => {
|
||||
process.env[ENV_FLAG] = 'maybe'
|
||||
expect(isCodexSystemDefaultRealHomeEnabled()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
/**
|
||||
* Routing truth for the SYSTEM-DEFAULT Codex account: it always runs against
|
||||
* the user's real ~/.codex; managed (multi-account) selections always get
|
||||
* their own self-contained homes. There is no user-facing setting — the
|
||||
* feature ships unconditionally.
|
||||
*
|
||||
* The env override exists only for test rigs (containment harness, e2e home
|
||||
* isolation, CDP verification) that must pin the legacy managed-home lane or
|
||||
* force the real-home lane inside a disposable HOME. It never appears in any
|
||||
* UI and no production path sets it.
|
||||
*/
|
||||
const CODEX_REAL_HOME_ENV_FLAG = 'ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME'
|
||||
|
||||
export function isCodexSystemDefaultRealHomeEnabled(): boolean {
|
||||
const envOverride = readCodexRealHomeEnvOverride()
|
||||
if (envOverride !== null) {
|
||||
return envOverride
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function readCodexRealHomeEnvOverride(): boolean | null {
|
||||
const raw = process.env[CODEX_REAL_HOME_ENV_FLAG]
|
||||
if (raw === undefined) {
|
||||
return null
|
||||
}
|
||||
const normalized = raw.trim().toLowerCase()
|
||||
if (normalized === '1' || normalized === 'true' || normalized === 'on') {
|
||||
return true
|
||||
}
|
||||
if (normalized === '0' || normalized === 'false' || normalized === 'off') {
|
||||
return false
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
|
@ -198,7 +198,6 @@ import {
|
|||
} from '../codex/codex-pane-account-registry'
|
||||
import { resolveCodexPaneLaunchAccount } from '../codex/codex-pane-launch-account'
|
||||
import { getSystemCodexHomePath } from '../codex/codex-home-paths'
|
||||
import { isCodexSystemDefaultRealHomeEnabled } from '../codex/codex-real-home-flag'
|
||||
import {
|
||||
environmentCodexHomeOverrideContextsEqual,
|
||||
getCustomCodexHomeOverrideForLaunch,
|
||||
|
|
@ -1117,10 +1116,7 @@ function shouldStripInheritedOrcaCodexHome(args: {
|
|||
settings: GlobalSettings | undefined
|
||||
}): boolean {
|
||||
return (
|
||||
args.target.runtime === 'host' &&
|
||||
args.selectedCodexHomePath === null &&
|
||||
!args.skipCodexHomeEnv &&
|
||||
isCodexSystemDefaultRealHomeEnabled()
|
||||
args.target.runtime === 'host' && args.selectedCodexHomePath === null && !args.skipCodexHomeEnv
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,12 +14,13 @@ async function readElectronHomeState(electronApp: ElectronApplication) {
|
|||
home: process.env.HOME,
|
||||
userProfile: process.env.USERPROFILE,
|
||||
codexHome: process.env.CODEX_HOME,
|
||||
orcaCodexHome: process.env.ORCA_CODEX_HOME,
|
||||
realHomeFlag: process.env.ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME
|
||||
orcaCodexHome: process.env.ORCA_CODEX_HOME
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Codex always routes to the real home now, so this single case covers both the
|
||||
// HOME boundary and that real-home routing lands inside the disposable profile.
|
||||
test('isolates Electron and Codex from the developer home by default', async ({ electronApp }) => {
|
||||
const state = await readElectronHomeState(electronApp)
|
||||
const expectedHome = path.join(state.userDataDir!, 'home')
|
||||
|
|
@ -30,17 +31,4 @@ test('isolates Electron and Codex from the developer home by default', async ({
|
|||
expect(state.userProfile).toBe(expectedHome)
|
||||
expect(state.codexHome).toBeUndefined()
|
||||
expect(state.orcaCodexHome).toBeUndefined()
|
||||
expect(state.realHomeFlag).toBe('0')
|
||||
})
|
||||
|
||||
test.describe('sandboxed real-home routing', () => {
|
||||
test.use({ codexRealHomeEnabled: true })
|
||||
|
||||
test('keeps flag-ON routing inside the disposable home', async ({ electronApp }) => {
|
||||
const state = await readElectronHomeState(electronApp)
|
||||
|
||||
expect(state.appHome).toBe(path.join(state.userDataDir!, 'home'))
|
||||
expect(state.nodeHome).toBe(path.join(state.userDataDir!, 'home'))
|
||||
expect(state.realHomeFlag).toBe('1')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -53,8 +53,7 @@ function createHeadlessLaunchIsolation(userDataDir: string): ElectronHomeIsolati
|
|||
ORCA_E2E_ENFORCE_SINGLE_INSTANCE_LOCK: '1'
|
||||
},
|
||||
extraEnv: {},
|
||||
userDataDir,
|
||||
codexRealHomeEnabled: false
|
||||
userDataDir
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -174,8 +174,7 @@ async function createComputerE2ERuntimeEnv(): Promise<NodeJS.ProcessEnv> {
|
|||
inheritedEnv,
|
||||
launchEnv: {},
|
||||
extraEnv: {},
|
||||
userDataDir,
|
||||
codexRealHomeEnabled: false
|
||||
userDataDir
|
||||
})
|
||||
return {
|
||||
...isolation.env,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ const RESTRICTED_ENV_KEYS = new Set([
|
|||
'HOMEPATH',
|
||||
'CODEX_HOME',
|
||||
'ORCA_CODEX_HOME',
|
||||
'ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME',
|
||||
'ORCA_E2E_USER_DATA_DIR',
|
||||
'ORCA_E2E_HOME_DIR',
|
||||
'ZDOTDIR',
|
||||
|
|
@ -23,7 +22,6 @@ type ElectronHomeIsolationOptions = {
|
|||
launchEnv: NodeJS.ProcessEnv
|
||||
extraEnv: Record<string, string>
|
||||
userDataDir: string
|
||||
codexRealHomeEnabled: boolean
|
||||
realHome?: string
|
||||
}
|
||||
|
||||
|
|
@ -50,9 +48,7 @@ function assertOverlayDoesNotReplaceIsolation(
|
|||
RESTRICTED_ENV_KEYS.has(key.toUpperCase())
|
||||
)
|
||||
if (restrictedKey) {
|
||||
throw new Error(
|
||||
`${overlayName}.${restrictedKey} cannot override the E2E home boundary; use codexRealHomeEnabled for sandboxed real-home coverage`
|
||||
)
|
||||
throw new Error(`${overlayName}.${restrictedKey} cannot override the E2E home boundary`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +63,6 @@ export function createElectronHomeIsolation({
|
|||
launchEnv,
|
||||
extraEnv,
|
||||
userDataDir,
|
||||
codexRealHomeEnabled,
|
||||
realHome = os.homedir()
|
||||
}: ElectronHomeIsolationOptions): ElectronHomeIsolation {
|
||||
assertOverlayDoesNotReplaceIsolation(launchEnv, 'launchEnv')
|
||||
|
|
@ -95,8 +90,7 @@ export function createElectronHomeIsolation({
|
|||
HOME: isolatedHome,
|
||||
USERPROFILE: isolatedHome,
|
||||
ORCA_E2E_USER_DATA_DIR: userDataDir,
|
||||
ORCA_E2E_HOME_DIR: isolatedHome,
|
||||
ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME: codexRealHomeEnabled ? '1' : '0'
|
||||
ORCA_E2E_HOME_DIR: isolatedHome
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ describe('createElectronHomeIsolation', () => {
|
|||
launchEnv: { TEST_TOKEN: 'safe' },
|
||||
extraEnv: { EXTRA_TEST_FLAG: '1' },
|
||||
userDataDir,
|
||||
codexRealHomeEnabled: false,
|
||||
realHome: '/real/home'
|
||||
})
|
||||
|
||||
|
|
@ -51,12 +50,16 @@ describe('createElectronHomeIsolation', () => {
|
|||
EXTRA_TEST_FLAG: '1',
|
||||
HOME: canonicalHome,
|
||||
USERPROFILE: canonicalHome,
|
||||
ORCA_E2E_USER_DATA_DIR: userDataDir,
|
||||
ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME: '0'
|
||||
ORCA_E2E_USER_DATA_DIR: userDataDir
|
||||
})
|
||||
expect(isolation.env.CODEX_HOME).toBeUndefined()
|
||||
expect(isolation.env.ORCA_CODEX_HOME).toBeUndefined()
|
||||
expect(isolation.env.ZDOTDIR).toBeUndefined()
|
||||
// Codex always routes to the resolved home, so the post-launch guard must
|
||||
// accept the boundary this env produces.
|
||||
expect(() =>
|
||||
assertElectronResolvedIsolatedHome(isolation.isolatedHome, isolation)
|
||||
).not.toThrow()
|
||||
})
|
||||
|
||||
it('rejects generic fixture overlays that could escape the boundary', () => {
|
||||
|
|
@ -66,7 +69,6 @@ describe('createElectronHomeIsolation', () => {
|
|||
launchEnv: { CODEX_HOME: '/unsafe' },
|
||||
extraEnv: {},
|
||||
userDataDir: createUserDataDir(),
|
||||
codexRealHomeEnabled: false,
|
||||
realHome: '/real/home'
|
||||
})
|
||||
).toThrow(/launchEnv\.CODEX_HOME/)
|
||||
|
|
@ -77,28 +79,11 @@ describe('createElectronHomeIsolation', () => {
|
|||
launchEnv: {},
|
||||
extraEnv: { ORCA_E2E_USER_DATA_DIR: '/unsafe' },
|
||||
userDataDir: createUserDataDir(),
|
||||
codexRealHomeEnabled: false,
|
||||
realHome: '/real/home'
|
||||
})
|
||||
).toThrow(/orcaAppExtraEnv\.ORCA_E2E_USER_DATA_DIR/)
|
||||
})
|
||||
|
||||
it('keeps real-home routing inside the disposable home when explicitly enabled', () => {
|
||||
const isolation = createElectronHomeIsolation({
|
||||
inheritedEnv: {},
|
||||
launchEnv: {},
|
||||
extraEnv: {},
|
||||
userDataDir: createUserDataDir(),
|
||||
codexRealHomeEnabled: true,
|
||||
realHome: '/real/home'
|
||||
})
|
||||
|
||||
expect(isolation.env.ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME).toBe('1')
|
||||
expect(() =>
|
||||
assertElectronResolvedIsolatedHome(isolation.isolatedHome, isolation)
|
||||
).not.toThrow()
|
||||
})
|
||||
|
||||
it('compares Windows home paths case-insensitively', () => {
|
||||
expect(areSameHomePath('C:\\Users\\Alice', 'c:\\users\\alice', 'win32')).toBe(true)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -201,8 +201,7 @@ export async function launchHeadlessPairedRuntimeHost(): Promise<HeadlessPairedR
|
|||
ORCA_E2E_HEADLESS: '1'
|
||||
},
|
||||
extraEnv: {},
|
||||
userDataDir,
|
||||
codexRealHomeEnabled: false
|
||||
userDataDir
|
||||
})
|
||||
const mainPath = path.join(process.cwd(), 'out', 'main', 'index.js')
|
||||
app = await electron.launch({
|
||||
|
|
|
|||
|
|
@ -55,9 +55,6 @@ type OrcaTestFixtures = {
|
|||
// memory benchmarks). Prepended before the main entry so Electron forwards
|
||||
// them to Chromium without affecting other specs' launches.
|
||||
orcaAppExtraArgs: string[]
|
||||
// Why: real-home E2E must still resolve inside the disposable fixture HOME.
|
||||
// Generic env overlays cannot opt out of that data-safety boundary.
|
||||
codexRealHomeEnabled: boolean
|
||||
// Why: a few IPC repro specs need to launch the Electron app with a scoped
|
||||
// PATH/token environment. Keep this fixture-owned so tests never mutate the
|
||||
// developer's shell or already-running Orca instance.
|
||||
|
|
@ -178,7 +175,6 @@ export const test = base.extend<OrcaTestFixtures, OrcaWorkerFixtures>({
|
|||
launchEnv,
|
||||
orcaAppExtraEnv,
|
||||
orcaAppExtraArgs,
|
||||
codexRealHomeEnabled,
|
||||
registerPostElectronShutdownCleanup
|
||||
},
|
||||
provideFixture,
|
||||
|
|
@ -212,8 +208,7 @@ export const test = base.extend<OrcaTestFixtures, OrcaWorkerFixtures>({
|
|||
inheritedEnv: cleanEnv,
|
||||
launchEnv,
|
||||
extraEnv: orcaAppExtraEnv,
|
||||
userDataDir,
|
||||
codexRealHomeEnabled
|
||||
userDataDir
|
||||
})
|
||||
// Why: ORCA_E2E_SLOWMO_MS adds a pause between every Playwright action so a
|
||||
// developer running with ORCA_E2E_FORCE_HEADFUL=1 can actually watch what
|
||||
|
|
@ -281,7 +276,6 @@ export const test = base.extend<OrcaTestFixtures, OrcaWorkerFixtures>({
|
|||
launchEnv: [{}, { option: true }],
|
||||
orcaAppExtraEnv: [{}, { option: true }],
|
||||
orcaAppExtraArgs: [[], { option: true }],
|
||||
codexRealHomeEnabled: [false, { option: true }],
|
||||
|
||||
// Test-scoped: grab the first BrowserWindow, add the test repo, and wait
|
||||
// until the session is fully ready with a worktree active.
|
||||
|
|
|
|||
|
|
@ -120,8 +120,7 @@ function createRestartLaunchIsolation(
|
|||
...(headful ? { ORCA_E2E_HEADFUL: '1' } : { ORCA_E2E_HEADLESS: '1' })
|
||||
},
|
||||
extraEnv: {},
|
||||
userDataDir,
|
||||
codexRealHomeEnabled: false
|
||||
userDataDir
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -155,8 +155,7 @@ export async function launchPairedElectronClient(
|
|||
inheritedEnv: cleanEnv,
|
||||
launchEnv: {},
|
||||
extraEnv: {},
|
||||
userDataDir,
|
||||
codexRealHomeEnabled: false
|
||||
userDataDir
|
||||
})
|
||||
const mainPath = path.join(process.cwd(), 'out', 'main', 'index.js')
|
||||
const app = await electron.launch({
|
||||
|
|
|
|||
|
|
@ -177,7 +177,6 @@ function runIteration({ exe, fixtureDir, timeoutMs, lingerMs }) {
|
|||
HOME: isolatedHome,
|
||||
USERPROFILE: isolatedHome,
|
||||
ORCA_E2E_HOME_DIR: isolatedHome,
|
||||
ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME: '0',
|
||||
ORCA_E2E_HEADLESS: '1'
|
||||
}
|
||||
delete env.CODEX_HOME
|
||||
|
|
|
|||
|
|
@ -289,8 +289,7 @@ async function main() {
|
|||
ORCA_E2E_USER_DATA_DIR: fixtureDir,
|
||||
HOME: isolatedHome,
|
||||
USERPROFILE: isolatedHome,
|
||||
ORCA_E2E_HOME_DIR: isolatedHome,
|
||||
ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME: '0'
|
||||
ORCA_E2E_HOME_DIR: isolatedHome
|
||||
}
|
||||
delete env.CODEX_HOME
|
||||
delete env.ORCA_CODEX_HOME
|
||||
|
|
|
|||
|
|
@ -364,7 +364,6 @@ function buildLaunchEnvironment({ fixtureDir, githubRepos, ghShimDir }) {
|
|||
HOME: isolatedHome,
|
||||
USERPROFILE: isolatedHome,
|
||||
ORCA_E2E_HOME_DIR: isolatedHome,
|
||||
ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME: '0',
|
||||
ORCA_E2E_HEADLESS: '1'
|
||||
}
|
||||
delete env.CODEX_HOME
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ const RESTRICTED_E2E_ENV_KEYS = new Set([
|
|||
'USERPROFILE',
|
||||
'CODEX_HOME',
|
||||
'ORCA_CODEX_HOME',
|
||||
'ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME',
|
||||
'ORCA_E2E_HOME_DIR',
|
||||
'ORCA_E2E_USER_DATA_DIR'
|
||||
])
|
||||
|
|
@ -92,8 +91,7 @@ export async function launchInstalledApp({
|
|||
ORCA_E2E_USER_DATA_DIR: userDataDir,
|
||||
HOME: isolatedHome,
|
||||
USERPROFILE: isolatedHome,
|
||||
ORCA_E2E_HOME_DIR: isolatedHome,
|
||||
ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME: '0'
|
||||
ORCA_E2E_HOME_DIR: isolatedHome
|
||||
}
|
||||
})
|
||||
// If firstWindow times out (the launched main never shows a window), the
|
||||
|
|
|
|||
Loading…
Reference in New Issue