fix(windows): stop blocking cmd.exe agent launches on a Codex preflight CLI call

On Windows every shell except cmd.exe installs the Codex launch preflight as a
deferred wrapper around the `codex` command, so it costs nothing at shell start.
cmd.exe has no equivalent, so the preflight was spliced into its `/K` startup
chain ahead of the agent command:

  cmd.exe /K "chcp 65001 > nul & if defined ORCA_CODEX_LAUNCH_PREFLIGHT
              call %..%%ORCA_CODEX_LAUNCH_PREFLIGHT%%..% agent hooks
              prepare-codex > nul 2>&1 & <agent command>"

That `call` runs the Orca CLI, which connects back into the running app over
IPC. Measured in-app it took 10.4s under spawn-time load (0.19s standalone), so
the agent could not start until it returned — pushing the tab-bar "+" launch
past its startup budget and failing intermittently. It also ran for every
cmd.exe pane, including plain terminals with no agent and no startup command.

The work itself is `codexHookService.install()` into a managed Codex home that
the main process already knows: local, idempotent, sub-millisecond. Do it in
buildPtyHostEnv where the preflight env is resolved, and drop the inline cmd
call and the ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE argv-quoting workaround it
needed. The deferred wrappers on POSIX/PowerShell/Git Bash are unchanged and
still cover a `codex` typed later in an already-open pane.
This commit is contained in:
OrcaWin
2026-08-24 08:58:22 -07:00
committed by Jinjing
parent f5fd7303ab
commit d68f60aff8
9 changed files with 72 additions and 64 deletions
@@ -231,12 +231,9 @@ describe('createPtySubprocess', () => {
expect(spawnMock).toHaveBeenCalledWith(
'cmd.exe',
[
'/K',
'chcp 65001 > nul & if defined ORCA_CODEX_LAUNCH_PREFLIGHT call %ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE%%ORCA_CODEX_LAUNCH_PREFLIGHT%%ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE% agent hooks prepare-codex > nul 2>&1'
],
['/K', 'chcp 65001 > nul'],
expect.objectContaining({
env: expect.objectContaining({ ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE: '"' })
env: expect.objectContaining({ ORCA_CODEX_LAUNCH_PREFLIGHT: CODEX_LAUNCH_PREFLIGHT })
})
)
})
@@ -297,10 +294,7 @@ describe('createPtySubprocess', () => {
expect(spawnMock).toHaveBeenCalledWith(
'cmd.exe',
[
'/K',
'chcp 65001 > nul & if defined ORCA_CODEX_LAUNCH_PREFLIGHT call %ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE%%ORCA_CODEX_LAUNCH_PREFLIGHT%%ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE% agent hooks prepare-codex > nul 2>&1'
],
['/K', 'chcp 65001 > nul'],
expect.any(Object)
)
expect(handle!.startupCommandDeliveredInShellArgs).toBeUndefined()
+1 -11
View File
@@ -22,10 +22,7 @@ import {
import { resolveProcessExitCause, type TerminalExitCause } from '../../shared/terminal-exit-cause'
import { signalPosixPtyForegroundGroup } from '../pty/posix-pty-foreground-group'
import { readPtsName } from '../pty/node-pty-pts-name'
import {
ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE_ENV,
resolveWindowsShellLaunchArgs
} from '../providers/windows-shell-args'
import { resolveWindowsShellLaunchArgs } from '../providers/windows-shell-args'
import {
resolveEffectiveWindowsPowerShell,
shouldProbeWindowsPowerShellAvailability,
@@ -747,13 +744,6 @@ export async function createPtySubprocess(opts: PtySubprocessOptions): Promise<S
}) ?? shellPath)
: shellPath
}
if (
pathWin32.basename(shellPath).toLowerCase() === 'cmd.exe' &&
env.ORCA_CODEX_LAUNCH_PREFLIGHT
) {
// Why: node-pty backslash-escapes argv quotes; expand the quote inside cmd.exe instead.
env[ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE_ENV] = '"'
}
// Why: a bare `pwsh.exe` resolves to the Store App Execution Alias stub whose launch fails with ERROR_ACCESS_DENIED (5).
windowsFallbackAttempts = buildWindowsPowerShellSpawnAttempts({
shellPath,
@@ -9,6 +9,11 @@ import { __setWindowsPathRegistryLoaderForTests } from '../pty/windows-path-regi
import { hasLiveClaudePtys, markClaudePtySpawned } from '../claude-accounts/live-pty-gate'
import { registerPtyHandlers, buildPtyHostEnv, clearProviderPtyState } from './pty'
const prepareManagedCodexHomeMock = vi.hoisted(() => vi.fn(() => null))
vi.mock('../codex/managed-home-shell-preflight', () => ({
prepareManagedCodexHomeBeforeShellLaunch: prepareManagedCodexHomeMock,
resolveManagedCodexShellPreflightHome: () => null
}))
vi.mock('electron', () => import('./pty-ipc-mock-registry').then((m) => m.electronModuleMock()))
vi.mock('fs', () => import('./pty-ipc-mock-registry').then((m) => m.fsModuleMock()))
vi.mock('node-pty', () => import('./pty-ipc-mock-registry').then((m) => m.nodePtyModuleMock()))
@@ -244,6 +249,34 @@ describe('registerPtyHandlers', () => {
// wrapper inherits, so the preflight must carry the CLI's verified absolute path.
expect(env.ORCA_CODEX_LAUNCH_PREFLIGHT).toBe(BUNDLED_CLI_PATH)
})
// Why: cmd.exe has no deferred `codex` wrapper, so its preflight ran inline at
// shell start as a CLI round-trip into this app — slow enough to push agent
// launches past their startup budget. Same prep, done in-process instead.
it('prepares the managed Codex home in-process instead of from the shell', async () => {
prepareManagedCodexHomeMock.mockClear()
const env = await withBundledCli(() =>
spawnAndGetEnv(undefined, undefined, () => TEST_CODEX_HOME)
)
expect(env.ORCA_CODEX_LAUNCH_PREFLIGHT).toBe(BUNDLED_CLI_PATH)
expect(prepareManagedCodexHomeMock).toHaveBeenCalledWith(
expect.objectContaining({
hooksEnabled: true,
env: expect.objectContaining({
CODEX_HOME: TEST_CODEX_HOME,
ORCA_CODEX_HOME: TEST_CODEX_HOME
})
})
)
})
it('does not prepare a managed Codex home when no preflight is installed', async () => {
prepareManagedCodexHomeMock.mockClear()
await withBundledCli(() => spawnAndGetEnv(undefined, undefined, () => TEST_CODEX_HOME), {
launcherExecutable: false
})
expect(prepareManagedCodexHomeMock).not.toHaveBeenCalled()
})
it('skips the Codex launch preflight when the bundled CLI is not executable', async () => {
const env = await withBundledCli(
() => spawnAndGetEnv(undefined, undefined, () => TEST_CODEX_HOME),
+12 -1
View File
@@ -61,7 +61,10 @@ import {
import { agentHookServer } from '../agent-hooks/server'
import { wslHookRelayManager } from '../agent-hooks/wsl-hook-relay-manager'
import { ensureWslHookRelayForReattach } from '../agent-hooks/wsl-hook-relay-reattach'
import { isAgentStatusHooksEnabled } from '../agent-hooks/managed-agent-hook-controls'
import {
isAgentStatusHooksEnabled,
prepareManagedCodexHomeBeforeShellLaunch
} from '../agent-hooks/managed-agent-hook-controls'
import { piTitlebarExtensionService } from '../pi/titlebar-extension-service'
import {
detectExplicitPiAgentKindFromCommand,
@@ -1933,6 +1936,14 @@ export function buildPtyHostEnv(
})
if (preflightCommand) {
baseEnv.ORCA_CODEX_LAUNCH_PREFLIGHT = preflightCommand
// Why: cmd.exe has no deferred `codex` wrapper, so its preflight ran inline
// at shell start — a CLI round-trip back into this app that stalled agent
// launches for seconds. Same idempotent prep, done in-process instead.
prepareManagedCodexHomeBeforeShellLaunch({
env: baseEnv,
userDataPath: opts.userDataPath,
hooksEnabled: true
})
} else {
delete baseEnv.ORCA_CODEX_LAUNCH_PREFLIGHT
}
@@ -580,7 +580,7 @@ describe('LocalPtyProvider', () => {
)
})
it('runs the Codex preflight once in the cmd.exe startup chain', async () => {
it('keeps the Codex preflight out of the cmd.exe startup chain', async () => {
const platform = Object.getOwnPropertyDescriptor(process, 'platform')
Object.defineProperty(process, 'platform', { value: 'win32' })
provider.configure({
@@ -601,14 +601,10 @@ describe('LocalPtyProvider', () => {
expect(spawnMock).toHaveBeenCalledWith(
'cmd.exe',
[
'/K',
'chcp 65001 > nul & if defined ORCA_CODEX_LAUNCH_PREFLIGHT call %ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE%%ORCA_CODEX_LAUNCH_PREFLIGHT%%ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE% agent hooks prepare-codex > nul 2>&1'
],
['/K', 'chcp 65001 > nul'],
expect.objectContaining({
env: expect.objectContaining({
ORCA_CODEX_LAUNCH_PREFLIGHT: CODEX_LAUNCH_PREFLIGHT,
ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE: '"'
ORCA_CODEX_LAUNCH_PREFLIGHT: CODEX_LAUNCH_PREFLIGHT
})
})
)
+2 -13
View File
@@ -1,10 +1,7 @@
/* eslint-disable max-lines -- Why: splitting spawn() would scatter tightly coupled PTY lifecycle logic (scan → ready → write → exit) with no cleaner ownership seam. */
import { basename, delimiter, win32 as pathWin32 } from 'node:path'
import { randomUUID } from 'node:crypto'
import {
ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE_ENV,
resolveWindowsShellLaunchArgs
} from './windows-shell-args'
import { resolveWindowsShellLaunchArgs } from './windows-shell-args'
import {
resolveEffectiveWindowsPowerShell,
shouldProbeWindowsPowerShellAvailability,
@@ -796,16 +793,8 @@ export class LocalPtyProvider implements IPtyProvider {
delete finalEnv.ORCA_CODEX_HOME
}
const shellBasename = pathWin32.basename(shellPath).toLowerCase()
const codexLaunchPreflightCommand = finalEnv.ORCA_CODEX_LAUNCH_PREFLIGHT
if (
codexLaunchPreflightCommand &&
(shellBasename === 'cmd.exe' || isWindowsGitBashShellPath(shellPath))
) {
if (shellBasename === 'cmd.exe') {
// Why: node-pty backslash-escapes argv quotes; expand the quote inside cmd.exe instead.
finalEnv[ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE_ENV] = '"'
}
if (codexLaunchPreflightCommand && isWindowsGitBashShellPath(shellPath)) {
const resolved = resolveWindowsShellLaunchArgs(
shellPath,
cwd,
+7 -13
View File
@@ -9,8 +9,6 @@ import { resolveWindowsShellLaunchArgs } from './windows-shell-args'
import { getShellReadyWrapperRoot } from './local-pty-shell-ready-wrapper-root'
const CODEX_LAUNCH_PREFLIGHT = 'C:\\Program Files\\Orca\\orca.exe'
const CMD_CODEX_LAUNCH_PREFLIGHT =
'if defined ORCA_CODEX_LAUNCH_PREFLIGHT call %ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE%%ORCA_CODEX_LAUNCH_PREFLIGHT%%ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE% agent hooks prepare-codex > nul 2>&1'
function expectedWslArgs(linuxCwd: string, distro?: string): string[] {
const command = `cd '${linuxCwd}' && export PATH="$HOME/.local/bin:$PATH" && ${buildWslInteractiveLoginShellCommand()}`
@@ -66,7 +64,7 @@ describe('resolveWindowsShellLaunchArgs', () => {
undefined,
CODEX_LAUNCH_PREFLIGHT
)
expect(result.shellArgs).toEqual(['/K', `chcp 65001 > nul & ${CMD_CODEX_LAUNCH_PREFLIGHT}`])
expect(result.shellArgs).toEqual(['/K', 'chcp 65001 > nul'])
expect(result.startupCommandDeliveredInShellArgs).toBeUndefined()
expect(result.effectiveCwd).toBe('C:\\Users\\alice')
expect(result.validationCwd).toBe('C:\\Users\\alice')
@@ -81,10 +79,7 @@ describe('resolveWindowsShellLaunchArgs', () => {
'codex --no-alt-screen',
CODEX_LAUNCH_PREFLIGHT
)
expect(result.shellArgs).toEqual([
'/K',
`chcp 65001 > nul & ${CMD_CODEX_LAUNCH_PREFLIGHT} & codex --no-alt-screen`
])
expect(result.shellArgs).toEqual(['/K', 'chcp 65001 > nul & codex --no-alt-screen'])
expect(result.startupCommandDeliveredInShellArgs).toBe(true)
})
@@ -111,7 +106,7 @@ describe('resolveWindowsShellLaunchArgs', () => {
`codex ${'x'.repeat(7000)}`,
CODEX_LAUNCH_PREFLIGHT
)
expect(result.shellArgs).toEqual(['/K', `chcp 65001 > nul & ${CMD_CODEX_LAUNCH_PREFLIGHT}`])
expect(result.shellArgs).toEqual(['/K', 'chcp 65001 > nul'])
expect(result.startupCommandDeliveredInShellArgs).toBeUndefined()
})
@@ -308,7 +303,7 @@ describe('resolveWindowsShellLaunchArgs', () => {
expect(result.startupCommandDeliveredInShellArgs).toBeUndefined()
})
it('quotes a spaced preflight path through each shell environment', () => {
it('keeps the Codex preflight out of cmd.exe args and in the Git Bash wrapper', () => {
const cmd = resolveWindowsShellLaunchArgs(
'cmd.exe',
'C:\\Users\\alice',
@@ -326,10 +321,9 @@ describe('resolveWindowsShellLaunchArgs', () => {
CODEX_LAUNCH_PREFLIGHT
)
expect(cmd.shellArgs[1]).toContain(
'call %ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE%%ORCA_CODEX_LAUNCH_PREFLIGHT%%ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE%'
)
expect(cmd.shellArgs[1]).not.toContain('"')
// Why: cmd.exe cannot defer the preflight behind a `codex` wrapper, so an
// inline call would block every pane on a CLI round-trip into the app.
expect(cmd.shellArgs[1]).not.toContain('prepare-codex')
expect(cmd.shellArgs[1]).not.toContain(CODEX_LAUNCH_PREFLIGHT)
const rcfilePath = getGitBashRcfilePath(gitBash.shellArgs[1])
expect(readFileSync(rcfilePath, 'utf8')).toContain('"${ORCA_CODEX_LAUNCH_PREFLIGHT}"')
+4 -3
View File
@@ -18,8 +18,6 @@ const CMD_EXE_COMMAND_LINE_MAX_CHARS = 8191
const STARTUP_COMMAND_TEXT_MAX_CHARS = 6000
const POWERSHELL_ENCODED_COMMAND_ARG_MAX_CHARS = 28_000
const CMD_UTF8_SETUP_COMMAND = 'chcp 65001 > nul'
export const ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE_ENV = 'ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE'
const CMD_CODEX_LAUNCH_PREFLIGHT = `if defined ORCA_CODEX_LAUNCH_PREFLIGHT call %${ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE_ENV}%%ORCA_CODEX_LAUNCH_PREFLIGHT%%${ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE_ENV}% agent hooks prepare-codex > nul 2>&1`
// Why: Git for Windows' bash inherits the ConPTY console's OEM code page
// (CP437), so a TUI that writes UTF-8 bytes straight to the console — agents
// like Claude Code use WriteFile, not WriteConsoleW — renders as mojibake
@@ -183,11 +181,14 @@ export function resolveWindowsShellLaunchArgs(
const shellBasename = pathWin32.basename(shellPath).toLowerCase()
const nativeCwd = normalizeWindowsTerminalCwd(cwd)
// Why no Codex preflight here: cmd.exe has no deferred-wrapper equivalent, so
// an inline `call orca agent hooks prepare-codex` would block the agent behind
// a CLI round-trip into the running app. buildPtyHostEnv does that prep
// in-process instead.
if (shellBasename === 'cmd.exe') {
const shellArgStartupCommand = getCmdShellArgStartupCommand(startupCommand)
const startupCommands = [
CMD_UTF8_SETUP_COMMAND,
...(codexLaunchPreflightCommand ? [CMD_CODEX_LAUNCH_PREFLIGHT] : []),
...(shellArgStartupCommand ? [shellArgStartupCommand] : [])
]
return {
@@ -12,10 +12,7 @@ import { join } from 'node:path'
import * as pty from 'node-pty'
import { afterEach, describe, expect, it } from 'vitest'
import { resolveGitBashPath } from '../git-bash'
import {
ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE_ENV,
resolveWindowsShellLaunchArgs
} from './windows-shell-args'
import { resolveWindowsShellLaunchArgs } from './windows-shell-args'
const describeWindows = process.platform === 'win32' ? describe : describe.skip
const tempDirs: string[] = []
@@ -116,7 +113,7 @@ afterEach(() => {
})
describeWindows('Windows Codex shell preflight runtime', () => {
it('runs a spaced executable through cmd.exe and continues after failure', async () => {
it('starts the cmd.exe startup command without blocking on the Codex preflight', async () => {
const root = makeTempDir()
const preflight = writeFailingPreflight(root)
const preflightMarker = join(root, 'cmd-preflight-ran')
@@ -130,6 +127,10 @@ describeWindows('Windows Codex shell preflight runtime', () => {
preflight
)
// Why: an inline preflight is a CLI round-trip into the running app; a slow
// one delayed the agent past its launch budget, so it must not be in argv.
expect(resolved.shellArgs.join(' ')).not.toContain('prepare-codex')
await runPty({
shellPath: 'cmd.exe',
shellArgs: resolved.shellArgs,
@@ -137,12 +138,11 @@ describeWindows('Windows Codex shell preflight runtime', () => {
env: {
...process.env,
ORCA_CODEX_LAUNCH_PREFLIGHT: preflight,
[ORCA_CODEX_LAUNCH_PREFLIGHT_CMD_QUOTE_ENV]: '"',
ORCA_PREFLIGHT_MARKER: preflightMarker
}
})
expect(readFileSync(preflightMarker, 'utf8')).toBe('ran')
expect(existsSync(preflightMarker)).toBe(false)
expect(readFileSync(startupMarker, 'utf8').trim()).toBe('launched')
})