Files
orca/src/main/codex/codex-pane-launch-account.ts
T
Brennan BensonandMerge Sim 5ff1aa540e fix(codex): re-land WSL direct-home cutover with counsel findings fixed (#16854)
* fix(codex): safely re-land WSL direct homes

* fix(codex): finish WSL direct-home cutover

* fix(codex): coalesce WSL launch hook installs

* perf(codex): avoid duplicate retired WSL session scan

* fix(codex): retain canonical WSL retired-home path

* fix(codex): fail closed before retiring WSL auth

* fix(codex): reopen WSL drain after rollback

* fix(codex): preserve WSL source on unknown panes

* fix(codex): harden repeated WSL runtime drains

* perf(codex): bound pending WSL session scans

* fix(codex): recover invalid WSL session watermarks

* fix(codex): validate retained WSL scan state

* fix(codex): accept durable WSL scan state

* test(codex): cover the drain's inode-identity guard against destination replacement

Removing the four `target_auth -ef temporary_destination_auth` assertions left
all 33 apply-script tests passing, so a regression deleting them would have
shipped silently. Reproduced before writing this.

A hash check cannot catch the case. The pinned hard link keeps the original
inode, so it still hashes correctly after another writer atomically renames a
different file over the destination path; only inode identity sees it. Without
the guard the script exits 0 and retires the source, leaving the user holding
bytes nothing validated. The new case asserts the source survives.

The harness is split by responsibility so no file exceeds its max-lines budget:
fixtures, the coreutils interference shims, the run types, the apply runner, and
the recovery/absent runners. The atomic-rename hook is deliberately separate
from the in-place rewrite shim because different guards catch them.

* fix(codex): keep the split drain harness inside the child-process boundaries

Extracting the harness into non-test modules moved it out of the exemptions the
single test file had: three new files import child_process, and two spawned
without windowsHide.

Adds the three to the import allowlist, and sets windowsHide on the spawns
rather than exempting them - the flag is correct for these calls regardless of
the ratchet, and they are skipped on win32 anyway.

---------

Co-authored-by: Merge Sim <sim@local>
2026-08-31 11:20:22 -07:00

166 lines
6.0 KiB
TypeScript

import type { GlobalSettings } from '../../shared/global-settings-types'
import { normalizeRuntimePathForComparison } from '../../shared/cross-platform-path'
import { parseWslUncPath } from '../../shared/wsl-paths'
import {
getCodexSelectionLaneKey,
getCodexSelectionTargetForAccount,
getSelectedCodexAccountIdForTarget,
type CodexAccountSelectionTarget
} from '../codex-accounts/runtime-selection'
import type { CodexPaneAccountRecord, CodexPaneHomeRoute } from './codex-pane-account-registry'
import type {
CodexEnvironmentHomeOverride,
CodexShellStartupHomeOverride
} from './codex-real-home-path'
type CodexPaneLaunchAccountSettings = Pick<
GlobalSettings,
'activeCodexManagedAccountId' | 'activeCodexManagedAccountIdsByRuntime' | 'codexManagedAccounts'
>
/**
* Resolves which Codex account a PTY is actually launching under.
*
* Why: an automatic session resume deliberately pins CODEX_HOME to the home
* that owns the session rather than to the current selection, so a cold-restored
* pane can come back on an account the user already switched away from. Naming
* that real account — instead of the selection Orca ignored — is what lets the
* restart prompt tell the user which account the pane is stuck on.
*
* Returns null when the launch cannot be attributed, which keeps the pane out of
* the stale-pane report entirely.
*/
export function resolveCodexPaneLaunchAccount(args: {
pinnedByResume: boolean
launchCodexHomePath: string | null
recordComparableHomeRoute?: boolean
shellStartupHomeOverride?: CodexShellStartupHomeOverride
environmentHomeOverride?: CodexEnvironmentHomeOverride
systemCodexHomePath: string
settings: CodexPaneLaunchAccountSettings
target: CodexAccountSelectionTarget
}): CodexPaneAccountRecord | null {
const selectionKey = getCodexSelectionLaneKey(args.target)
const resolvedHomeRoute = resolveCodexPaneHomeRoute(args)
const homeRoute =
args.recordComparableHomeRoute === false && resolvedHomeRoute === 'shared-home'
? 'custom-home'
: resolvedHomeRoute
if (!args.pinnedByResume) {
return {
selectionKey,
accountId: getSelectedCodexAccountIdForTarget(args.settings, args.target),
...(homeRoute ? { homeRoute } : {}),
...(args.shellStartupHomeOverride
? { shellStartupHomeOverride: args.shellStartupHomeOverride }
: {}),
...(args.environmentHomeOverride
? { environmentHomeOverride: args.environmentHomeOverride }
: {})
}
}
const accountId = resolveCodexHomeOwnerAccountId(args)
return accountId === undefined
? null
: {
selectionKey,
accountId,
...(homeRoute ? { homeRoute } : {}),
...(args.shellStartupHomeOverride
? { shellStartupHomeOverride: args.shellStartupHomeOverride }
: {}),
...(args.environmentHomeOverride
? { environmentHomeOverride: args.environmentHomeOverride }
: {})
}
}
function resolveCodexPaneHomeRoute(args: {
launchCodexHomePath: string | null
systemCodexHomePath: string
settings: CodexPaneLaunchAccountSettings
target: CodexAccountSelectionTarget
}): CodexPaneHomeRoute {
if (
!args.launchCodexHomePath ||
normalizeRuntimePathForComparison(args.launchCodexHomePath) ===
normalizeRuntimePathForComparison(args.systemCodexHomePath)
) {
return 'real-home'
}
const launchHomePath = args.launchCodexHomePath
const accountOwnsHome = args.settings.codexManagedAccounts?.some((account) =>
accountOwnsCodexHome(account, args.target, launchHomePath)
)
if (accountOwnsHome) {
return 'account-home'
}
if (args.target.runtime === 'wsl') {
return parseWslUncPath(args.launchCodexHomePath)?.linuxPath.endsWith('/.codex')
? 'real-home'
: 'wsl-home'
}
return 'shared-home'
}
/** undefined when no account owns the home; null means the system-default account. */
function resolveCodexHomeOwnerAccountId(args: {
launchCodexHomePath: string | null
systemCodexHomePath: string
settings: CodexPaneLaunchAccountSettings
target: CodexAccountSelectionTarget
}): string | null | undefined {
// Why: no injected CODEX_HOME means Codex reads the user's own home.
if (!args.launchCodexHomePath) {
return null
}
if (
normalizeRuntimePathForComparison(args.launchCodexHomePath) ===
normalizeRuntimePathForComparison(args.systemCodexHomePath)
) {
return null
}
const launchHomePath = args.launchCodexHomePath
const owner = args.settings.codexManagedAccounts?.find((account) =>
accountOwnsCodexHome(account, args.target, launchHomePath)
)
// Why: an unowned home cannot be named, and naming the account a pane is stuck
// on is the prompt's whole job — so decline rather than guess. A wrong notice
// silently drops every keystroke in that terminal. Note the shared runtime
// mirror only hot-swaps to the current selection on the legacy flag-OFF lane;
// a pane resumed into it after the per-account rollout is genuinely stale but
// still unnameable, so that cohort stays unreported.
return owner ? owner.id : undefined
}
function accountOwnsCodexHome(
account: NonNullable<CodexPaneLaunchAccountSettings['codexManagedAccounts']>[number],
target: CodexAccountSelectionTarget,
launchHomePath: string
): boolean {
// Why: a WSL pane resolves its account from its own per-distro lane, so a
// host account's home must never answer for it (and vice versa).
if (
getCodexSelectionLaneKey(getCodexSelectionTargetForAccount(account)) !==
getCodexSelectionLaneKey(target)
) {
return false
}
if (
normalizeRuntimePathForComparison(account.managedHomePath) ===
normalizeRuntimePathForComparison(launchHomePath)
) {
return true
}
const launchWslHome = target.runtime === 'wsl' ? parseWslUncPath(launchHomePath) : null
const accountDistro = account.wslDistro?.trim()
const accountLinuxHome = account.wslLinuxHomePath?.trim()
return Boolean(
launchWslHome &&
accountDistro &&
accountLinuxHome &&
launchWslHome.distro.toLowerCase() === accountDistro.toLowerCase() &&
launchWslHome.linuxPath === accountLinuxHome
)
}