fix(codex): resolve the launch preflight to a verified absolute Orca CLI path (STA-4270)

The Codex launch preflight carried a bare command name ('orca' / 'orca-dev') in
ORCA_CODEX_LAUNCH_PREFLIGHT. The codex() wrapper that invokes it is emitted after
the user's profile scripts are sourced, and those routinely rewrite PATH, so the
name was resolved against a PATH Orca neither controls nor can predict.

Resolve and verify the shipped CLI's absolute path instead, and return null when
no path verifies so the preflight is skipped rather than run against an
unidentified program.
This commit is contained in:
Brennan Benson
2026-08-13 22:21:20 -07:00
parent 070572bfd7
commit 5c4ed35a0c
8 changed files with 477 additions and 33 deletions
+23
View File
@@ -0,0 +1,23 @@
import { join } from 'node:path'
// Why `orca-ide` on Linux: GNOME Orca ships /usr/bin/orca, so the CLI never claims that name.
export const LINUX_CLI_COMMAND_NAME = 'orca-ide'
/** Absolute path of the CLI launcher this app ships in its own resources bundle.
* Lives apart from cli-installer so callers that only need the path (PTY env
* assembly) don't pull in the installer's `electron` dependency. */
export function getBundledLauncherPath(
platform: NodeJS.Platform,
resourcesPath: string
): string | null {
if (platform === 'darwin') {
return join(resourcesPath, 'bin', 'orca')
}
if (platform === 'linux') {
return join(resourcesPath, 'bin', LINUX_CLI_COMMAND_NAME)
}
if (platform === 'win32') {
return join(resourcesPath, 'bin', 'orca.exe')
}
return null
}
+4 -18
View File
@@ -19,6 +19,7 @@ import { promisify } from 'node:util'
import type { CliInstallMethod, CliInstallStatus } from '../../shared/cli-install-types'
import { expandWindowsEnvironmentVariables } from '../../shared/windows-environment-expansion'
import { buildAppImageCliWrapper } from './appimage-cli-wrapper'
import { getBundledLauncherPath, LINUX_CLI_COMMAND_NAME } from './bundled-cli-launcher-path'
import {
invalidateWindowsUserPathRegistryCache,
readFreshWindowsUserPathRegistry,
@@ -29,7 +30,6 @@ import {
const execFileAsync = promisify(execFile)
const DEFAULT_MAC_COMMAND_PATH = '/usr/local/bin/orca'
const DEV_COMMAND_NAME = 'orca-dev'
const LINUX_COMMAND_NAME = 'orca-ide'
const LEGACY_LINUX_COMMAND_NAME = 'orca'
const DEV_LAUNCHER_DIR = ['cli', 'bin']
const WINDOWS_PATH_WRITE_TIMEOUT_MS = 5_000
@@ -88,7 +88,7 @@ export class CliInstaller {
return DEV_COMMAND_NAME
}
// Why: packaged Linux uses `orca-ide` to avoid shadowing GNOME Orca's /usr/bin/orca.
return this.platform === 'linux' ? LINUX_COMMAND_NAME : 'orca'
return this.platform === 'linux' ? LINUX_CLI_COMMAND_NAME : 'orca'
}
constructor(options: CliInstallerOptions = {}) {
@@ -351,7 +351,7 @@ export class CliInstaller {
if (this.platform === 'linux') {
// Why: Linux lacks a privileged global command flow; ~/.local/bin is the least-surprising user-scoped dir.
// Why `orca-ide`: GNOME Orca ships /usr/bin/orca, so avoid shadowing that screen reader.
return join(this.homePath, '.local', 'bin', LINUX_COMMAND_NAME)
return join(this.homePath, '.local', 'bin', LINUX_CLI_COMMAND_NAME)
}
if (this.platform === 'win32') {
@@ -1200,18 +1200,4 @@ function quotePowerShell(value: string): string {
return `'${value.replaceAll("'", "''")}'`
}
export function getBundledLauncherPath(
platform: NodeJS.Platform,
resourcesPath: string
): string | null {
if (platform === 'darwin') {
return join(resourcesPath, 'bin', 'orca')
}
if (platform === 'linux') {
return join(resourcesPath, 'bin', LINUX_COMMAND_NAME)
}
if (platform === 'win32') {
return join(resourcesPath, 'bin', 'orca.exe')
}
return null
}
export { getBundledLauncherPath }
+73 -3
View File
@@ -17,9 +17,12 @@ import { AGENT_SESSION_CLAIM_DIGEST_VERSION } from '../../shared/agent-session-h
import { PtyWriteUnavailableError } from '../providers/pty-write-unavailable-error'
import { TerminalSessionOwnerUnverifiedError } from '../daemon/daemon-errors'
import type * as Wsl from '../wsl'
import { getBundledLauncherPath } from '../cli/bundled-cli-launcher-path'
const isWindowsHost = process.platform === 'win32'
const posixOnlyIt = isWindowsHost ? it.skip : it
const BUNDLED_RESOURCES_PATH = join('/tmp', 'orca-bundled-resources')
const BUNDLED_CLI_PATH = getBundledLauncherPath(process.platform, BUNDLED_RESOURCES_PATH) as string
// Why: bare shells no longer mkdir ~/.omp; OMP status lives under userData (#10196).
const expectedOmpStatusExtension = posix.join(
'/tmp/orca-user-data',
@@ -133,7 +136,8 @@ vi.mock('fs', () => ({
writeFileSync: writeFileSyncMock,
chmodSync: chmodSyncMock,
constants: {
X_OK: 1
X_OK: 1,
R_OK: 4
}
}))
@@ -1939,6 +1943,42 @@ describe('registerPtyHandlers', () => {
]
}
// Why: the Codex launch preflight now carries the bundled CLI's verified absolute
// path, so these cases need a resources root whose launcher passes the exec check.
async function withBundledCli<T>(
run: () => Promise<T>,
options?: { launcherExecutable?: boolean }
): Promise<T> {
const launcherExecutable = options?.launcherExecutable ?? true
const previousResourcesPath = process.resourcesPath
Object.defineProperty(process, 'resourcesPath', {
configurable: true,
value: BUNDLED_RESOURCES_PATH
})
// Why: only teach the launcher path to look like an executable file; every other
// stat/access keeps the permissive default the rest of the harness relies on.
statSyncMock.mockImplementation((target: string) => ({
isDirectory: () => target !== BUNDLED_CLI_PATH,
isFile: () => target === BUNDLED_CLI_PATH,
mode: 0o755
}))
if (!launcherExecutable) {
accessSyncMock.mockImplementation((target: string) => {
if (target === BUNDLED_CLI_PATH) {
throw new Error(`EACCES: ${target}`)
}
})
}
try {
return await run()
} finally {
Object.defineProperty(process, 'resourcesPath', {
configurable: true,
value: previousResourcesPath
})
}
}
describe('spawn environment', () => {
it('refreshes the outer Windows PATH for a WSL spawn without forwarding it', async () => {
const originalPlatform = process.platform
@@ -2133,10 +2173,40 @@ describe('registerPtyHandlers', () => {
})
it('injects the selected Codex home into Orca terminal PTYs', async () => {
const env = await spawnAndGetEnv(undefined, undefined, () => TEST_CODEX_HOME)
const env = await withBundledCli(() =>
spawnAndGetEnv(undefined, undefined, () => TEST_CODEX_HOME)
)
expect(env.CODEX_HOME).toBe(TEST_CODEX_HOME)
expect(env.ORCA_CODEX_HOME).toBe(TEST_CODEX_HOME)
expect(env.ORCA_CODEX_LAUNCH_PREFLIGHT).toBe('orca')
// Why (STA-4270): a bare name would be resolved by the post-profile PATH the codex()
// wrapper inherits, so the preflight must carry the CLI's verified absolute path.
expect(env.ORCA_CODEX_LAUNCH_PREFLIGHT).toBe(BUNDLED_CLI_PATH)
})
it('skips the Codex launch preflight when the bundled CLI is not executable', async () => {
const env = await withBundledCli(
() => spawnAndGetEnv(undefined, undefined, () => TEST_CODEX_HOME),
{ launcherExecutable: false }
)
expect(env.CODEX_HOME).toBe(TEST_CODEX_HOME)
expect(env.ORCA_CODEX_LAUNCH_PREFLIGHT).toBeUndefined()
})
// Why (STA-4270): profile scripts run before the codex() wrapper and routinely prepend
// directories to PATH, so a scratch `orca` there must never become the preflight.
it('pins the Codex launch preflight to the bundled CLI even when PATH leads elsewhere', async () => {
const env = await withBundledCli(() =>
spawnAndGetEnv(
{ PATH: `/tmp/hijack-scratch${delimiter}/usr/bin` },
undefined,
() => TEST_CODEX_HOME
)
)
expect(env.ORCA_CODEX_LAUNCH_PREFLIGHT).toBe(BUNDLED_CLI_PATH)
expect(env.ORCA_CODEX_LAUNCH_PREFLIGHT).not.toBe('orca')
expect(env.ORCA_CODEX_LAUNCH_PREFLIGHT.startsWith('/tmp/hijack-scratch')).toBe(false)
})
it('does not install the Codex launch preflight when Codex hooks are disabled', async () => {
+3 -1
View File
@@ -1863,7 +1863,9 @@ export function buildPtyHostEnv(
hooksEnabled: opts.codexStatusHooksEnabled ?? opts.agentStatusHooksEnabled,
isPackaged: opts.isPackaged,
isWsl: opts.isWsl,
managedHomePath: opts.selectedCodexHomePath
managedHomePath: opts.selectedCodexHomePath,
userDataPath: opts.userDataPath,
resourcesPath: opts.resourcesPath
})
if (preflightCommand) {
baseEnv.ORCA_CODEX_LAUNCH_PREFLIGHT = preflightCommand
@@ -0,0 +1,127 @@
import { execFileSync } from 'node:child_process'
import { chmodSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { delimiter, join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { getBundledLauncherPath } from '../cli/bundled-cli-launcher-path'
import { getDaemonBashShellReadyRcfileContent } from '../daemon/shell-ready'
import { resolveCodexShellLaunchPreflightCommand } from './codex-shell-launch-preflight'
// Why (STA-4270): the generated rcfile sources /etc/profile and the user's profile
// *before* it defines the codex() wrapper, so a profile PATH prepend decides what an
// unqualified preflight command resolves to. This drives the real generated rcfile
// through a real bash and uses filesystem markers — not scraped output — as the oracle.
const roots: string[] = []
const bashAvailable = process.platform !== 'win32' && existsSync('/bin/bash')
type Fixture = {
root: string
resourcesPath: string
intendedMarker: string
hijackMarker: string
codexMarker: string
homePath: string
}
function writeStub(path: string, markerPath: string): void {
mkdirSync(join(path, '..'), { recursive: true })
writeFileSync(path, `#!/bin/sh\nprintf '%s\\n' "$*" > ${JSON.stringify(markerPath)}\n`)
chmodSync(path, 0o755)
}
function buildFixture(): Fixture {
const root = mkdtempSync(join(tmpdir(), 'orca-codex-profile-path-'))
roots.push(root)
const resourcesPath = join(root, 'resources')
const hijackDir = join(root, 'hijack')
const codexDir = join(root, 'codex-bin')
const homePath = join(root, 'home')
const intendedMarker = join(root, 'intended-ran')
const hijackMarker = join(root, 'hijack-ran')
const codexMarker = join(root, 'codex-ran')
mkdirSync(hijackDir, { recursive: true })
mkdirSync(codexDir, { recursive: true })
mkdirSync(homePath, { recursive: true })
// The CLI Orca ships, at the absolute path Orca controls.
writeStub(getBundledLauncherPath(process.platform, resourcesPath) as string, intendedMarker)
// The impostor a user's own bin directory could hold under every CLI name Orca uses.
for (const name of ['orca', 'orca-ide', 'orca-dev']) {
writeStub(join(hijackDir, name), hijackMarker)
}
writeStub(join(codexDir, 'codex'), codexMarker)
// Why: /etc/profile runs first and macOS path_helper rebuilds PATH from /etc/paths,
// so the profile must re-prepend both dirs to model a real user's dotfile winning.
writeFileSync(
join(homePath, '.bash_profile'),
`export PATH=${JSON.stringify(codexDir)}:"$PATH"\nexport PATH=${JSON.stringify(hijackDir)}:"$PATH"\n`
)
return { root, resourcesPath, intendedMarker, hijackMarker, codexMarker, homePath }
}
function launchCodexThroughRcfile(fixture: Fixture, preflightValue: string): void {
const rcfilePath = join(fixture.root, 'rcfile')
writeFileSync(rcfilePath, getDaemonBashShellReadyRcfileContent(), 'utf8')
execFileSync('/bin/bash', ['--rcfile', rcfilePath, '-i', '-c', 'codex --version'], {
stdio: 'ignore',
env: {
HOME: fixture.homePath,
// Why: the hijack dir is deliberately absent here — only the profile adds it.
PATH: ['/usr/bin', '/bin', '/usr/sbin', '/sbin'].join(delimiter),
TERM: 'dumb',
SHELL: '/bin/bash',
ORCA_SHELL_READY_MARKER: '0',
ORCA_SHELL_STARTUP_IDENTITY: '0',
ORCA_CODEX_HOME: join(fixture.root, 'codex-home'),
ORCA_CODEX_LAUNCH_PREFLIGHT: preflightValue
}
})
}
afterEach(() => {
for (const root of roots.splice(0)) {
rmSync(root, { recursive: true, force: true })
}
})
describe.skipIf(!bashAvailable)('Codex preflight under a profile-rewritten PATH', () => {
it('sources the user profile before emitting the codex wrapper', () => {
const rcfile = getDaemonBashShellReadyRcfileContent()
expect(rcfile.indexOf('source "$HOME/.bash_profile"')).toBeLessThan(
rcfile.indexOf('ORCA_CODEX_LAUNCH_PREFLIGHT')
)
})
it('runs the bundled CLI the resolver picked, not the impostor the profile put first', () => {
const fixture = buildFixture()
const preflightCommand = resolveCodexShellLaunchPreflightCommand({
hooksEnabled: true,
isPackaged: true,
managedHomePath: join(fixture.root, 'codex-home'),
userDataPath: join(fixture.root, 'user-data'),
resourcesPath: fixture.resourcesPath
})
expect(preflightCommand).toBe(getBundledLauncherPath(process.platform, fixture.resourcesPath))
launchCodexThroughRcfile(fixture, preflightCommand as string)
expect(existsSync(fixture.intendedMarker)).toBe(true)
expect(existsSync(fixture.hijackMarker)).toBe(false)
// Guard against a vacuous pass: the wrapper must still have reached `command codex`.
expect(existsSync(fixture.codexMarker)).toBe(true)
})
// Why: pins the defect itself, so a regression back to an unqualified name fails here.
it('would run the impostor if the preflight carried an unqualified command name', () => {
const fixture = buildFixture()
launchCodexThroughRcfile(fixture, 'orca')
expect(existsSync(fixture.hijackMarker)).toBe(true)
expect(existsSync(fixture.intendedMarker)).toBe(false)
expect(existsSync(fixture.codexMarker)).toBe(true)
})
})
@@ -1,6 +1,6 @@
import { chmodSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { delimiter, join } from 'node:path'
import { delimiter, isAbsolute, join } from 'node:path'
import { execFileSync, spawnSync } from 'node:child_process'
import { afterEach, describe, expect, it } from 'vitest'
import {
@@ -198,21 +198,112 @@ describe('PowerShell Codex shell launch preflight', () => {
})
describe('Codex shell launch preflight command', () => {
it('uses the packaged or development CLI only for native managed homes', () => {
function makeCliRoot(): { root: string; userDataPath: string; resourcesPath: string } {
const root = mkdtempSync(join(tmpdir(), 'orca-codex-preflight-cli-'))
roots.push(root)
const userDataPath = join(root, 'user-data')
const resourcesPath = join(root, 'resources')
mkdirSync(join(userDataPath, 'cli', 'bin'), { recursive: true })
mkdirSync(join(resourcesPath, 'bin'), { recursive: true })
return { root, userDataPath, resourcesPath }
}
it.each([
{ platform: 'darwin' as const, bundled: 'orca' },
{ platform: 'linux' as const, bundled: 'orca-ide' },
{ platform: 'win32' as const, bundled: 'orca.exe' }
])('carries the verified bundled $platform launcher as an absolute path', (config) => {
const { userDataPath, resourcesPath } = makeCliRoot()
const launcherPath = join(resourcesPath, 'bin', config.bundled)
writeExecutable(launcherPath, '#!/bin/sh\nexit 0\n')
expect(
resolveCodexShellLaunchPreflightCommand({
hooksEnabled: true,
isPackaged: true,
managedHomePath: '/managed/home'
managedHomePath: '/managed/home',
userDataPath,
resourcesPath,
platform: config.platform
})
).toBe('orca')
).toBe(launcherPath)
})
it('carries the verified dev launcher as an absolute path', () => {
const { userDataPath, resourcesPath } = makeCliRoot()
const launcherPath = join(userDataPath, 'cli', 'bin', 'orca-dev')
writeExecutable(launcherPath, '#!/bin/sh\nexit 0\n')
expect(
resolveCodexShellLaunchPreflightCommand({
hooksEnabled: true,
isPackaged: false,
managedHomePath: '/managed/home'
managedHomePath: '/managed/home',
userDataPath,
resourcesPath,
platform: 'darwin'
})
).toBe('orca-dev')
).toBe(launcherPath)
})
it('never returns an unqualified command name that a profile-rewritten PATH could hijack', () => {
const { userDataPath, resourcesPath } = makeCliRoot()
writeExecutable(join(resourcesPath, 'bin', 'orca'), '#!/bin/sh\nexit 0\n')
writeExecutable(join(userDataPath, 'cli', 'bin', 'orca-dev'), '#!/bin/sh\nexit 0\n')
for (const isPackaged of [true, false]) {
const command = resolveCodexShellLaunchPreflightCommand({
hooksEnabled: true,
isPackaged,
managedHomePath: '/managed/home',
userDataPath,
resourcesPath,
platform: 'darwin'
})
expect(command).not.toBeNull()
expect(isAbsolute(command as string)).toBe(true)
}
})
it.each([
{ label: 'the launcher file is missing', create: null },
{ label: 'the launcher is not executable', create: 0o644 },
{ label: 'the launcher path is a directory', create: 'directory' as const }
])('skips the preflight when $label', (config) => {
const { userDataPath, resourcesPath } = makeCliRoot()
const launcherPath = join(resourcesPath, 'bin', 'orca')
if (config.create === 'directory') {
mkdirSync(launcherPath)
} else if (config.create !== null) {
writeFileSync(launcherPath, '#!/bin/sh\nexit 0\n')
chmodSync(launcherPath, config.create)
}
expect(
resolveCodexShellLaunchPreflightCommand({
hooksEnabled: true,
isPackaged: true,
managedHomePath: '/managed/home',
userDataPath,
resourcesPath,
platform: 'darwin'
})
).toBeNull()
})
it('skips the preflight when the packaged build exposes no resources root', () => {
const { userDataPath } = makeCliRoot()
expect(
resolveCodexShellLaunchPreflightCommand({
hooksEnabled: true,
isPackaged: true,
managedHomePath: '/managed/home',
userDataPath,
resourcesPath: null,
platform: 'darwin'
})
).toBeNull()
})
it.each([
@@ -220,6 +311,103 @@ describe('Codex shell launch preflight command', () => {
{ hooksEnabled: true, isWsl: true, managedHomePath: '/managed/home' },
{ hooksEnabled: true, isWsl: false, managedHomePath: null }
])('does not enable an unsupported preflight for %o', (options) => {
expect(resolveCodexShellLaunchPreflightCommand({ ...options, isPackaged: true })).toBeNull()
const { userDataPath, resourcesPath } = makeCliRoot()
writeExecutable(join(resourcesPath, 'bin', 'orca'), '#!/bin/sh\nexit 0\n')
expect(
resolveCodexShellLaunchPreflightCommand({
...options,
isPackaged: true,
userDataPath,
resourcesPath,
platform: 'darwin'
})
).toBeNull()
})
})
// Why: the resolved value is now an absolute path, and app bundles (macOS) and
// Program Files (Windows) both put spaces in it.
describe.skipIf(process.platform === 'win32')('Codex preflight paths containing spaces', () => {
function writeSpacedPreflight(root: string): { preflightPath: string; markerPath: string } {
const dir = join(root, 'Orca Dev.app', 'Contents', 'Resources', 'bin')
mkdirSync(dir, { recursive: true })
const markerPath = join(root, 'preflight-ran')
const preflightPath = join(dir, 'orca')
writeExecutable(
preflightPath,
`#!/bin/sh\n[ "$1 $2 $3" = "agent hooks prepare-codex" ] || exit 2\nprintf ran > ${JSON.stringify(markerPath)}\n`
)
return { preflightPath, markerPath }
}
it('invokes a POSIX preflight whose absolute path contains spaces', () => {
const root = mkdtempSync(join(tmpdir(), 'orca-codex-spaced-posix-'))
roots.push(root)
const bin = join(root, 'bin')
mkdirSync(bin)
writeExecutable(join(bin, 'codex'), '#!/bin/sh\nexit 0\n')
const { preflightPath, markerPath } = writeSpacedPreflight(root)
execFileSync(
'/bin/bash',
['--noprofile', '--norc', '-c', `${getPosixCodexShellLaunchPreflight()}\ncodex`],
{
env: {
...process.env,
PATH: `${bin}${delimiter}${process.env.PATH ?? ''}`,
ORCA_CODEX_LAUNCH_PREFLIGHT: preflightPath
}
}
)
expect(existsSync(markerPath)).toBe(true)
})
it.skipIf(!fishAvailable)('invokes a fish preflight whose absolute path contains spaces', () => {
const root = mkdtempSync(join(tmpdir(), 'orca-codex-spaced-fish-'))
roots.push(root)
const bin = join(root, 'bin')
mkdirSync(bin)
writeExecutable(join(bin, 'codex'), '#!/bin/sh\nexit 0\n')
const { preflightPath, markerPath } = writeSpacedPreflight(root)
execFileSync('fish', ['--no-config', '-c', `${getFishCodexShellLaunchPreflight()}\ncodex`], {
env: {
...process.env,
PATH: `${bin}${delimiter}${process.env.PATH ?? ''}`,
ORCA_CODEX_LAUNCH_PREFLIGHT: preflightPath
}
})
expect(existsSync(markerPath)).toBe(true)
})
it.skipIf(!pwshAvailable)(
'invokes a PowerShell preflight whose absolute path contains spaces',
() => {
const root = mkdtempSync(join(tmpdir(), 'orca-codex-spaced-pwsh-'))
roots.push(root)
const bin = join(root, 'bin')
mkdirSync(bin)
writeExecutable(join(bin, 'codex'), '#!/bin/sh\nprintf launched\n')
const { preflightPath, markerPath } = writeSpacedPreflight(root)
const result = spawnSync(
'pwsh',
['-NoLogo', '-NoProfile', '-Command', `${getPowerShellCodexShellLaunchPreflight()}\ncodex`],
{
encoding: 'utf-8',
env: {
...process.env,
PATH: `${bin}${delimiter}${process.env.PATH ?? ''}`,
ORCA_CODEX_LAUNCH_PREFLIGHT: preflightPath
}
}
)
expect(result.status, result.stderr).toBe(0)
expect(existsSync(markerPath)).toBe(true)
}
)
})
+51 -3
View File
@@ -1,13 +1,61 @@
export function resolveCodexShellLaunchPreflightCommand(options: {
import { accessSync, constants, statSync } from 'node:fs'
import { join } from 'node:path'
import { getBundledLauncherPath } from '../cli/bundled-cli-launcher-path'
const DEV_LAUNCHER_DIR = ['cli', 'bin']
const DEV_COMMAND_NAME = 'orca-dev'
export type CodexShellLaunchPreflightCommandOptions = {
hooksEnabled: boolean
isPackaged: boolean
isWsl?: boolean
managedHomePath: string | null
}): string | null {
/** Where the dev launcher is written; `join(userDataPath, 'cli', 'bin')` is also what managed dev PTYs prepend to PATH. */
userDataPath: string
/** Packaged app resources root; the bundled launcher lives under it. */
resourcesPath?: string | null
/** Test seam. */
platform?: NodeJS.Platform
}
/** Absolute path of the Orca CLI the preflight must execute, or null to skip it.
*
* Why absolute: the value rides in ORCA_CODEX_LAUNCH_PREFLIGHT and is invoked
* from the codex() wrapper, which shell-ready emits *after* the user's profile
* scripts run. Those scripts routinely rewrite PATH, so an unqualified name
* would be resolved against a PATH Orca neither controls nor can predict —
* handing Orca's managed Codex environment to an unidentified program. When no
* path verifies, skipping the preflight is the predictable degradation. */
export function resolveCodexShellLaunchPreflightCommand(
options: CodexShellLaunchPreflightCommandOptions
): string | null {
if (!options.hooksEnabled || options.isWsl || !options.managedHomePath) {
return null
}
return options.isPackaged ? 'orca' : 'orca-dev'
const platform = options.platform ?? process.platform
const candidate = options.isPackaged
? options.resourcesPath
? getBundledLauncherPath(platform, options.resourcesPath)
: null
: join(
options.userDataPath,
...DEV_LAUNCHER_DIR,
platform === 'win32' ? `${DEV_COMMAND_NAME}.cmd` : DEV_COMMAND_NAME
)
return candidate && isExecutableFileOnDisk(candidate, platform) ? candidate : null
}
function isExecutableFileOnDisk(path: string, platform: NodeJS.Platform): boolean {
try {
if (!statSync(path).isFile()) {
return false
}
// Why: Windows has no exec bit, so a readable launcher file is the strongest signal available.
accessSync(path, platform === 'win32' ? constants.R_OK : constants.X_OK)
return true
} catch {
return false
}
}
export function getPosixCodexShellLaunchPreflight(): string {
+1 -1
View File
@@ -47,7 +47,7 @@ describe('addOrcaWslInteropEnv', () => {
ORCA_TERMINAL_HANDLE: 'term_wsl',
ORCA_USER_DATA_PATH: 'C:\\Users\\jin\\AppData\\Roaming\\Orca',
ORCA_CLI_COMMAND: 'orca-ide',
ORCA_CODEX_LAUNCH_PREFLIGHT: 'orca',
ORCA_CODEX_LAUNCH_PREFLIGHT: 'C:\\Program Files\\Orca\\resources\\bin\\orca.exe',
ORCA_OMP_STATUS_EXTENSION: 'C:\\Users\\jin\\.omp\\agent\\extensions\\orca-agent-status.ts',
ORCA_PRIME_AGENT_STATUS_EXTENSION: 'C:\\stale\\orca-agent-status.ts',
ORCA_PANE_KEY: 'tab-1:leaf-1',