diff --git a/src/main/codex/codex-app-server-process-tree-kill.ts b/src/main/codex/codex-app-server-process-tree-kill.ts index 315246aaa9f..1d8d0f10bda 100644 --- a/src/main/codex/codex-app-server-process-tree-kill.ts +++ b/src/main/codex/codex-app-server-process-tree-kill.ts @@ -32,8 +32,8 @@ export function killCodexAppServerProcessTree( return } try { - // Why: npm-installed Codex runs behind cmd.exe; killing only that wrapper - // leaves the app-server child alive after a timeout or failed shutdown. + // Why: npm-installed Codex runs behind a launcher (node, or cmd.exe for an unresolved shim); + // killing only that wrapper leaves the app-server child alive after a timeout. const killer = spawnImpl('taskkill', ['/pid', String(child.pid), '/t', '/f'], { stdio: 'ignore', windowsHide: true diff --git a/src/main/codex/codex-app-server-session.ts b/src/main/codex/codex-app-server-session.ts index 067fdd6d6b7..389cd20516c 100644 --- a/src/main/codex/codex-app-server-session.ts +++ b/src/main/codex/codex-app-server-session.ts @@ -25,7 +25,7 @@ export type CodexAppServerInvocation = { * * Required, and `null` only for a guest-side launcher (wsl.exe) where the host * path means nothing. Optional would let a native builder omit it and silently - * fall back to pairing against a cmd.exe wrapper with no type error. + * skip the pairing with no type error. */ cliPath: string | null /** Overlay applied on top of the inherited environment (e.g. CODEX_HOME). */ diff --git a/src/main/codex/codex-model-catalog-probe.ts b/src/main/codex/codex-model-catalog-probe.ts index 66914f51991..2cf354259d7 100644 --- a/src/main/codex/codex-model-catalog-probe.ts +++ b/src/main/codex/codex-model-catalog-probe.ts @@ -1,4 +1,3 @@ -import { getSpawnArgsForWindows } from '../win32-utils' import { CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS } from '../codex-cli/codex-read-only-app-server-args' import { runCodexAppServerSession } from './codex-app-server-session' import { fetchCodexModelCatalogListing } from './codex-structured-model-catalog' @@ -44,14 +43,11 @@ export function createCodexModelCatalogProbe( ): AgentModelCatalogProbe { return async (accountHomePath: string): Promise => { const { command, environment } = await resolveCodexStructuredInvocation(deps) - const { spawnCmd, spawnArgs } = getSpawnArgsForWindows(command, [ - ...CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS - ]) const run = deps.runSession ?? runCodexAppServerSession const listing = await run( { - command: spawnCmd, - args: spawnArgs, + command, + args: [...CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS], cliPath: command, env: { ...definedEnv(environment), CODEX_HOME: accountHomePath }, timeoutMs: CODEX_MODEL_CATALOG_PROBE_TIMEOUT_MS diff --git a/src/main/codex/codex-session-index-heal.ts b/src/main/codex/codex-session-index-heal.ts index 364263f2f47..5ce55c8e082 100644 --- a/src/main/codex/codex-session-index-heal.ts +++ b/src/main/codex/codex-session-index-heal.ts @@ -2,7 +2,6 @@ import { dirname, join } from 'node:path' import { resolveCodexCommand } from '../codex-cli/command' import { isTransientSqliteContention } from '../sqlite/sqlite-read-failure' import { CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS } from '../codex-cli/codex-read-only-app-server-args' -import { getSpawnArgsForWindows } from '../win32-utils' import { getCodexSessionBackfillStateDirPath } from './codex-home-paths' import { resolveCodexSessionBackfillPaths } from './codex-session-backfill' import { @@ -291,14 +290,11 @@ export function buildNativeHealInvocation( timeoutMs: number ): CodexAppServerInvocation { const command = resolveCodexCommand() - // Why: each session is torn down after one batch, so plugin startup could - // leave marketplace clones running; indexing needs neither plugins nor tools. - const { spawnCmd, spawnArgs } = getSpawnArgsForWindows(command, [ - ...CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS - ]) return { - command: spawnCmd, - args: spawnArgs, + command, + // Why: each session is torn down after one batch, so plugin startup could + // leave marketplace clones running; indexing needs neither plugins nor tools. + args: [...CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS], cliPath: command, // Why: pin the home explicitly — nested Orca launches can inherit a managed // CODEX_HOME from the daemon environment, which would index the wrong sqlite DB. diff --git a/src/main/codex/codex-short-lived-app-server-spawn.test.ts b/src/main/codex/codex-short-lived-app-server-spawn.test.ts new file mode 100644 index 00000000000..cdb67ff4640 --- /dev/null +++ b/src/main/codex/codex-short-lived-app-server-spawn.test.ts @@ -0,0 +1,74 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS } from '../codex-cli/codex-read-only-app-server-args' +import type { CodexAppServerInvocation } from './codex-app-server-session' + +const NPM_CODEX_SHIM = 'C:\\Users\\alice\\AppData\\Roaming\\npm\\codex.cmd' + +vi.mock('../codex-cli/command', () => ({ resolveCodexCommand: () => NPM_CODEX_SHIM })) + +import { buildNativeHealInvocation } from './codex-session-index-heal' +import { resolveNativeCodexTrustGrantHost } from './codex-trust-grant-host' +import { createCodexModelCatalogProbe } from './codex-model-catalog-probe' + +// Why: spawnProcess resolves npm's codex.cmd past cmd.exe, but only when it is handed the shim. +// A builder that pre-wraps it in `cmd.exe /d /c` puts cmd.exe back into every short-lived +// Codex launch — hundreds of them while a large history indexes. +describe('short-lived Codex app-server invocations on Windows', () => { + const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform') + + beforeEach(() => { + Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) + }) + + afterEach(() => { + if (originalPlatform) { + Object.defineProperty(process, 'platform', originalPlatform) + } + }) + + it('hands the index-heal session the shim itself', () => { + const invocation = buildNativeHealInvocation('C:\\homes\\a', 1_000) + expect(invocation.command).toBe(NPM_CODEX_SHIM) + expect(invocation.cliPath).toBe(NPM_CODEX_SHIM) + expect(invocation.args).toEqual([...CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS]) + }) + + it('hands the hook trust grant session the shim itself', () => { + const request = resolveNativeCodexTrustGrantHost().buildRequest({ + runtimeHomePath: 'C:\\homes\\a', + managedCommand: 'orca-hook', + expectedTrustKeys: [] + }) + expect(request.invocation.command).toBe(NPM_CODEX_SHIM) + expect(request.invocation.args).toEqual(['app-server']) + }) + + it('hands the model catalog probe session the shim itself', async () => { + const invocations: CodexAppServerInvocation[] = [] + const probe = createCodexModelCatalogProbe({ + resolveEnvironment: async () => ({ PATH: 'C:\\bin' }), + resolveCommand: () => NPM_CODEX_SHIM, + runSession: async (invocation, body) => { + invocations.push(invocation) + return body({ + request: async () => ({ + data: [ + { + model: 'gpt-live', + displayName: 'GPT Live', + hidden: false, + supportedReasoningEfforts: [{ reasoningEffort: 'high' }], + isDefault: true + } + ], + nextCursor: null + }), + notify: () => {} + }) + } + }) + await probe('C:\\homes\\a') + expect(invocations[0]?.command).toBe(NPM_CODEX_SHIM) + expect(invocations[0]?.args).toEqual([...CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS]) + }) +}) diff --git a/src/main/codex/codex-state-db-backfill-recovery.test.ts b/src/main/codex/codex-state-db-backfill-recovery.test.ts index 1986ccbdae9..cc20eef34df 100644 --- a/src/main/codex/codex-state-db-backfill-recovery.test.ts +++ b/src/main/codex/codex-state-db-backfill-recovery.test.ts @@ -5,7 +5,6 @@ import { join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' import * as ownerIdentity from '../agent-hooks/managed-hook-owner-identity' import { CODEX_READ_ONLY_APP_SERVER_ARGS } from '../codex-cli/codex-read-only-app-server-args' -import { getCmdExePath } from '../win32-utils' import { _internals, resolveCodexBackfillSupervisorLockRoot, @@ -365,7 +364,7 @@ describe('Codex state DB backfill recovery', () => { expect(terminate).toHaveBeenCalledWith(child) }) - it('uses batch-safe read-only arguments for native Windows recovery', async () => { + it('hands the Codex shim itself to the spawn chokepoint for native Windows recovery', async () => { Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) const child = createFakeChild() const spawnProcess = vi.fn(() => child) @@ -393,11 +392,9 @@ describe('Codex state DB backfill recovery', () => { string[], { cwd?: string; env?: NodeJS.ProcessEnv } ] - expect(spawnFile).toBe(getCmdExePath()) + // Why: spawnProcess resolves a recognised npm shim past cmd.exe; pre-wrapping hides the shim. + expect(spawnFile).toBe(codexCommand) expect(spawnArgs).toEqual([ - '/d', - '/c', - codexCommand, '-c', 'approval_policy=never', '-s', diff --git a/src/main/codex/codex-state-db-backfill-recovery.ts b/src/main/codex/codex-state-db-backfill-recovery.ts index 16d56d5907f..b4acef3b337 100644 --- a/src/main/codex/codex-state-db-backfill-recovery.ts +++ b/src/main/codex/codex-state-db-backfill-recovery.ts @@ -1,4 +1,3 @@ -import { spawn, type ChildProcess } from 'node:child_process' import { createHash } from 'node:crypto' import { join } from 'node:path' import { setTimeout as delay } from 'node:timers/promises' @@ -11,7 +10,11 @@ import { resolveCodexCommand } from '../codex-cli/command' import { withCliRuntimeOnPath } from '../../shared/node-cli-command-resolution' import { CODEX_READ_ONLY_APP_SERVER_ARGS } from '../codex-cli/codex-read-only-app-server-args' import { terminateCodexProbeChild } from '../rate-limits/codex-probe-termination' -import { getSpawnArgsForWindows } from '../win32-utils' +import type { ChildProcessHandle } from '../../shared/child-process/process-spec' +import { + spawnCodexAppServerProcess, + type CodexAppServerSpawn +} from './codex-app-server-process-tree-kill' import { getOrcaUserDataPath } from './codex-home-paths' import { BACKFILL_PENDING_MIN_SESSION_FILES, @@ -34,17 +37,17 @@ export type CodexStateDbBackfillRecoverySummary = { } type RecoveryDependencies = { - spawnProcess: typeof spawn + spawnProcess: CodexAppServerSpawn resolveCommand: () => string readStatus: (codexHomePath: string) => CodexStateDbBackfillStatus countSessions: (sessionsRoot: string, limit: number) => number now: () => number sleep: (ms: number, signal: AbortSignal) => Promise - terminate: (child: ChildProcess) => Promise + terminate: (child: ChildProcessHandle) => Promise } const defaultDependencies: RecoveryDependencies = { - spawnProcess: spawn, + spawnProcess: spawnCodexAppServerProcess, resolveCommand: resolveCodexCommand, readStatus: readCodexStateDbBackfillStatus, countSessions: countCodexSessionFilesUpTo, @@ -86,7 +89,7 @@ function initialRecoveryDecision( function spawnRecoveryProcess( codexHomePath: string, dependencies: RecoveryDependencies -): ChildProcess { +): ChildProcessHandle { const wslHome = process.platform === 'win32' ? parseWslUncPath(codexHomePath) : null if (wslHome) { return dependencies.spawnProcess( @@ -104,10 +107,7 @@ function spawnRecoveryProcess( ) } const command = dependencies.resolveCommand() - const { spawnCmd, spawnArgs } = getSpawnArgsForWindows(command, [ - ...CODEX_READ_ONLY_APP_SERVER_ARGS - ]) - return dependencies.spawnProcess(spawnCmd, spawnArgs, { + return dependencies.spawnProcess(command, [...CODEX_READ_ONLY_APP_SERVER_ARGS], { cwd: codexHomePath, stdio: ['pipe', 'ignore', 'ignore'], windowsHide: true, diff --git a/src/main/codex/codex-trust-grant-host.ts b/src/main/codex/codex-trust-grant-host.ts index 9228fad43ce..d7fb8e45975 100644 --- a/src/main/codex/codex-trust-grant-host.ts +++ b/src/main/codex/codex-trust-grant-host.ts @@ -1,6 +1,5 @@ import { runProcess } from '../../shared/child-process/run-process' import { resolveCodexCommand } from '../codex-cli/command' -import { getSpawnArgsForWindows } from '../win32-utils' import { buildWslCodexAppServerArgs, buildWslCodexIdentityProbe, @@ -73,12 +72,11 @@ export function resolveNativeCodexTrustGrantHost(): ResolvedCodexTrustGrantHost return { binaryStamp: command === 'codex' ? null : buildNativeCodexBinaryStamp(command), buildRequest: (input) => { - const { spawnCmd, spawnArgs } = getSpawnArgsForWindows(command, ['app-server']) const useDefaultCodexHome = input.useDefaultCodexHome === true return { invocation: { - command: spawnCmd, - args: spawnArgs, + command, + args: ['app-server'], cliPath: command, ...(useDefaultCodexHome ? { envToDelete: ['CODEX_HOME'] } diff --git a/src/main/rate-limits/codex-fetcher-process-contract.test.ts b/src/main/rate-limits/codex-fetcher-process-contract.test.ts index 8d067a686ad..b698c706c6a 100644 --- a/src/main/rate-limits/codex-fetcher-process-contract.test.ts +++ b/src/main/rate-limits/codex-fetcher-process-contract.test.ts @@ -2,18 +2,27 @@ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type * as Win32Utils from '../win32-utils' +import type * as RunProcess from '../../shared/child-process/run-process' -const { getSpawnArgsForWindowsMock, ptySpawnMock, resolveCodexCommandMock } = vi.hoisted(() => ({ - getSpawnArgsForWindowsMock: vi.fn(), +const { ptySpawnMock, resolveCodexCommandMock, stubScript } = vi.hoisted(() => ({ + stubScript: { path: '' }, ptySpawnMock: vi.fn(), resolveCodexCommandMock: vi.fn() })) -vi.mock('../win32-utils', async (importOriginal) => ({ - ...(await importOriginal()), - getSpawnArgsForWindows: getSpawnArgsForWindowsMock -})) +// Runs the node stub in place of the resolved CLI, through the real chokepoint. +vi.mock('../../shared/child-process/run-process', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + spawnProcess: (spec: Parameters[0]) => + actual.spawnProcess({ + ...spec, + program: process.execPath, + args: [stubScript.path, ...(spec.args ?? [])] + }) + } +}) vi.mock('../codex-cli/command', () => ({ resolveCodexCommand: resolveCodexCommandMock @@ -105,10 +114,7 @@ describe('Codex rate-limit process contract', () => { previousExpectedHome = process.env.ORCA_EXPECTED_CODEX_HOME process.env.ORCA_EXPECTED_CODEX_HOME = join(tempRoot, 'managed-codex-home') resolveCodexCommandMock.mockReturnValue('codex') - getSpawnArgsForWindowsMock.mockImplementation((_command: string, args: string[]) => ({ - spawnCmd: process.execPath, - spawnArgs: [stubPath, ...args] - })) + stubScript.path = stubPath }) afterEach(() => { diff --git a/src/main/rate-limits/codex-fetcher-rpc-exit-diagnostics.test.ts b/src/main/rate-limits/codex-fetcher-rpc-exit-diagnostics.test.ts index ee874ef5942..441be8d836d 100644 --- a/src/main/rate-limits/codex-fetcher-rpc-exit-diagnostics.test.ts +++ b/src/main/rate-limits/codex-fetcher-rpc-exit-diagnostics.test.ts @@ -2,19 +2,28 @@ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type * as Win32Utils from '../win32-utils' +import type * as RunProcess from '../../shared/child-process/run-process' import { isCodexAuthError } from '../../shared/codex-auth-errors' -const { getSpawnArgsForWindowsMock, ptySpawnMock, resolveCodexCommandMock } = vi.hoisted(() => ({ - getSpawnArgsForWindowsMock: vi.fn(), +const { ptySpawnMock, resolveCodexCommandMock, stubScript } = vi.hoisted(() => ({ + stubScript: { path: '' }, ptySpawnMock: vi.fn(), resolveCodexCommandMock: vi.fn() })) -vi.mock('../win32-utils', async (importOriginal) => ({ - ...(await importOriginal()), - getSpawnArgsForWindows: getSpawnArgsForWindowsMock -})) +// Runs the node stub in place of the resolved CLI, through the real chokepoint. +vi.mock('../../shared/child-process/run-process', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + spawnProcess: (spec: Parameters[0]) => + actual.spawnProcess({ + ...spec, + program: process.execPath, + args: [stubScript.path, ...(spec.args ?? [])] + }) + } +}) vi.mock('../codex-cli/command', () => ({ resolveCodexCommand: resolveCodexCommandMock @@ -62,10 +71,7 @@ describe('Codex RPC exit diagnostics', () => { stubPath = join(tempRoot, 'codex-exit-stub.cjs') writeFileSync(stubPath, STUB_CODEX_SOURCE) resolveCodexCommandMock.mockReturnValue('codex') - getSpawnArgsForWindowsMock.mockImplementation((_command: string, args: string[]) => ({ - spawnCmd: process.execPath, - spawnArgs: [stubPath, ...args] - })) + stubScript.path = stubPath }) afterEach(() => { diff --git a/src/main/rate-limits/codex-fetcher-runtime-pairing.test.ts b/src/main/rate-limits/codex-fetcher-runtime-pairing.test.ts index 0353bd4c31d..d825bb65fd8 100644 --- a/src/main/rate-limits/codex-fetcher-runtime-pairing.test.ts +++ b/src/main/rate-limits/codex-fetcher-runtime-pairing.test.ts @@ -3,6 +3,7 @@ import { EventEmitter } from 'node:events' import { tmpdir } from 'node:os' import { delimiter, join } from 'node:path' import { beforeEach, describe, expect, it, vi } from 'vitest' +import type * as RunProcess from '../../shared/child-process/run-process' const { childSpawnMock, readFileMock, resolveCodexCommandMock, ptySpawnMock } = vi.hoisted(() => ({ childSpawnMock: vi.fn(), @@ -12,6 +13,12 @@ const { childSpawnMock, readFileMock, resolveCodexCommandMock, ptySpawnMock } = })) vi.mock('node:child_process', () => ({ spawn: childSpawnMock })) +// The chokepoint is the seam: assertions see what the fetcher asked for, before shim resolution. +vi.mock('../../shared/child-process/run-process', async (importOriginal) => ({ + ...(await importOriginal()), + spawnProcess: (spec: { program: string; args?: readonly string[] }) => + childSpawnMock(spec.program, spec.args ?? [], spec) +})) vi.mock('node:fs/promises', () => ({ readFile: readFileMock })) vi.mock('../codex-cli/command', () => ({ resolveCodexCommand: resolveCodexCommandMock })) vi.mock('node-pty', () => ({ spawn: ptySpawnMock })) @@ -79,18 +86,17 @@ describe('codex rate-limit spawn runtime pairing', () => { await vi.advanceTimersByTimeAsync(0) const spawnEnv = childSpawnMock.mock.calls[0]?.[2]?.env as NodeJS.ProcessEnv - // Guards the argument choice: pairing spawnCmd (cmd.exe on win32) rather than - // the resolved CLI silently reverts the ABI fix (stablyai/orca#10932). + // Guards the argument choice: pairing anything but the resolved CLI silently + // reverts the ABI fix (stablyai/orca#10932). expect(spawnEnv.PATH?.split(delimiter)[0]).toBe(bin) rpcChild.emit('close') await resultPromise }) - it('pairs the resolved CLI on win32, where the spawn command is cmd.exe', async () => { - // Why win32 specifically: on posix getSpawnArgsForWindows returns the CLI - // itself, so pairing the spawn command instead of the resolved CLI is - // indistinguishable. Only here does the wrong argument become cmd.exe. + it('hands an npm .cmd shim to the spawn chokepoint unwrapped, paired with its node', async () => { + // Why win32 specifically: only here could a pre-wrap turn the program into + // cmd.exe, which hides the shim from spawnProcess's resolver. const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform') Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) try { @@ -103,7 +109,7 @@ describe('codex rate-limit spawn runtime pairing', () => { await vi.advanceTimersByTimeAsync(0) const spawnCommand = childSpawnMock.mock.calls[0]?.[0] as string - expect(spawnCommand.toLowerCase()).toContain('cmd.exe') + expect(spawnCommand).toBe(cli) const spawnEnv = childSpawnMock.mock.calls[0]?.[2]?.env as NodeJS.ProcessEnv expect((spawnEnv.Path ?? spawnEnv.PATH)?.split(';')[0]).toBe(bin) diff --git a/src/main/rate-limits/codex-fetcher.test.ts b/src/main/rate-limits/codex-fetcher.test.ts index be6edd3b3fb..7d5b7423f7c 100644 --- a/src/main/rate-limits/codex-fetcher.test.ts +++ b/src/main/rate-limits/codex-fetcher.test.ts @@ -1,6 +1,7 @@ import { EventEmitter } from 'node:events' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type * as RunProcess from '../../shared/child-process/run-process' const { childSpawnMock, @@ -22,6 +23,13 @@ vi.mock('node:child_process', () => ({ spawn: childSpawnMock })) +// The chokepoint is the seam: assertions see what the fetcher asked for, before shim resolution. +vi.mock('../../shared/child-process/run-process', async (importOriginal) => ({ + ...(await importOriginal()), + spawnProcess: (spec: { program: string; args?: readonly string[] }) => + childSpawnMock(spec.program, spec.args ?? [], spec) +})) + vi.mock('node:fs/promises', () => ({ readFile: readFileMock })) @@ -51,7 +59,6 @@ vi.mock('./codex-auth-presence', () => ({ import { fetchCodexRateLimits } from './codex-fetcher' import { probeCodexAuthPresence } from './codex-auth-presence' import { getActiveHiddenRateLimitPtyCount } from './hidden-pty-cleanup' -import { getCmdExePath } from '../win32-utils' import { CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS } from '../codex-cli/codex-read-only-app-server-args' function makeDisposable() { @@ -787,13 +794,9 @@ describe('fetchCodexRateLimits', () => { await resultPromise const [spawnFile, spawnArgs, spawnOptions] = childSpawnMock.mock.calls[0] - expect(spawnFile).toBe(getCmdExePath()) - expect(spawnArgs).toEqual([ - '/d', - '/c', - codexCommand, - ...CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS - ]) + // Pre-wrapping in cmd.exe would hide the npm shim from spawnProcess's resolver. + expect(spawnFile).toBe(codexCommand) + expect(spawnArgs).toEqual([...CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS]) expect(spawnOptions).toEqual( expect.objectContaining({ env: expect.objectContaining({ CODEX_HOME: 'C:\\Users\\alice\\.codex' }) diff --git a/src/main/rate-limits/codex-fetcher.ts b/src/main/rate-limits/codex-fetcher.ts index 1217500fb2c..804cd22c660 100644 --- a/src/main/rate-limits/codex-fetcher.ts +++ b/src/main/rate-limits/codex-fetcher.ts @@ -1,5 +1,4 @@ import type { CodexRateLimitResetOutcome, ProviderRateLimits } from '../../shared/rate-limit-types' -import { spawn } from 'node:child_process' import { isCodexAuthError } from '../../shared/codex-auth-errors' import { buildWslExecArgs, buildWslLoginShellCommand } from '../../shared/wsl-login-shell-command' import { parseWslUncPath } from '../../shared/wsl-paths' @@ -18,7 +17,8 @@ import { import { isCodexStateDbBackfillPending } from '../codex/codex-state-db' import { startCodexStateDbBackfillRecoveryInBackground } from '../codex/codex-state-db-backfill-recovery' import { withMacTailscaleDnsHint } from '../network/macos-tailscale-dns-diagnostic' -import { getCmdExePath, getSpawnArgsForWindows } from '../win32-utils' +import { spawnProcess } from '../../shared/child-process/run-process' +import { getCmdExePath } from '../win32-utils' import { probeCodexAuthPresence } from './codex-auth-presence' import { fetchCodexRateLimitsViaBackend, @@ -109,19 +109,17 @@ async function fetchViaRpc(options?: CodexRateLimitFetchOptions): Promise