Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/commandResolution.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { spawnSync } from 'node:child_process'
import { existsSync } from 'node:fs'
import { homedir } from 'node:os'
import { delimiter, join } from 'node:path'
import { spawnSyncCommand } from './utils/commandInvocation.js'

export type CommandInvocation = {
command: string
Expand Down Expand Up @@ -61,7 +61,7 @@ function getPotentialCodexExecutables(prefix: string): string[] {
'codex-win32-x64',
'vendor',
'x86_64-pc-windows-msvc',
'codex',
'bin',
'codex.exe',
)
: join(packageDir, 'bin', 'codex')
Expand All @@ -86,7 +86,7 @@ function getPotentialRipgrepExecutables(prefix: string): string[] {
}

export function canRunCommand(command: string, args: string[] = []): boolean {
const result = spawnSync(command, args, {
const result = spawnSyncCommand(command, args, {
stdio: 'ignore',
windowsHide: true,
})
Expand Down
18 changes: 16 additions & 2 deletions src/server/accountRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { mkdtemp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from '
import type { IncomingMessage, ServerResponse } from 'node:http'
import { homedir, tmpdir } from 'node:os'
import { join } from 'node:path'
import { resolveCodexCommand } from '../commandResolution.js'
import { getSpawnInvocation } from '../utils/commandInvocation.js'
import { buildAppServerArgs } from './appServerRuntimeConfig.js'
import { callRpcWithRateLimitDecodeRecovery } from './rateLimitDecodeRecovery.js'

Expand Down Expand Up @@ -650,7 +652,13 @@ async function withTemporaryCodexAppServer<T>(
const authPath = join(tempCodexHome, 'auth.json')
await writeFile(authPath, authRaw, { encoding: 'utf8', mode: 0o600 })

const proc = spawn('codex', buildAppServerArgs(), {
const codexCommand = resolveCodexCommand()
if (!codexCommand) {
throw new Error('Codex CLI is not available. Install @openai/codex or set CODEXUI_CODEX_COMMAND.')
}
Comment on lines +655 to +658

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Temp auth.json residue 🐞 Bug ⛨ Security

withTemporaryCodexAppServer() creates a temporary CODEX_HOME and writes auth.json before
checking resolveCodexCommand(); when Codex is unavailable it throws before reaching the
try/finally that deletes the temp directory, leaving auth material on disk. This can accumulate temp
directories and persist credentials unexpectedly on failure paths.
Agent Prompt
### Issue description
`withTemporaryCodexAppServer()` creates a temp directory and writes `auth.json` before verifying Codex is available. If `resolveCodexCommand()` returns null, the function throws before entering the later `try/finally` cleanup, so the temp directory (containing `auth.json`) is never removed.

### Issue Context
This is a failure-path regression introduced by moving Codex availability checking into this function and placing it after temp file creation.

### Fix Focus Areas
- src/server/accountRoutes.ts[647-664]
- src/server/accountRoutes.ts[770-792]

### Suggested fix
Resolve Codex **before** creating/writing the temporary `CODEX_HOME` directory, or wrap temp dir creation/writes in an outer `try/finally` that removes `tempCodexHome` when any error occurs before the existing `dispose()` cleanup is guaranteed to run.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


const invocation = getSpawnInvocation(codexCommand, buildAppServerArgs())
const proc = spawn(invocation.command, invocation.args, {
env: { ...process.env, CODEX_HOME: tempCodexHome },
stdio: ['pipe', 'pipe', 'pipe'],
})
Expand Down Expand Up @@ -1027,7 +1035,13 @@ async function startCodexLogin(): Promise<string> {
return await waitForLoginUrl()
}

const proc = spawn('codex', ['login'], {
const codexCommand = resolveCodexCommand()
if (!codexCommand) {
throw new Error('Codex CLI is not available. Install @openai/codex or set CODEXUI_CODEX_COMMAND.')
}

const invocation = getSpawnInvocation(codexCommand, ['login'])
const proc = spawn(invocation.command, invocation.args, {
env: process.env,
stdio: ['pipe', 'pipe', 'pipe'],
})
Expand Down