diff --git a/test/codex-session-lifecycle.test.ts b/test/codex-session-lifecycle.test.ts index cf67908fc..46ac8c496 100644 --- a/test/codex-session-lifecycle.test.ts +++ b/test/codex-session-lifecycle.test.ts @@ -121,6 +121,23 @@ process.stdout.write(JSON.stringify({ type: 'turn.completed', usage: { input_tok }); }); + test('CODEX_HOME is never under the child temp dir, so Codex can create its PATH aliases', async () => { + const record = path.join(os.tmpdir(), `codex-lifecycle-child-env-${process.pid}.json`); + try { + await withFakeCodex(` +fs.writeFileSync(${JSON.stringify(record)}, JSON.stringify({ tmp: process.env.TMPDIR, codexHome: process.env.CODEX_HOME })); +`, async ({ skillDir }) => { + const result = await runCodexSkill({ skillDir, prompt: 'fixture', timeoutMs: 2_000 }); + expect(result.exitCode).toBe(0); + const child = JSON.parse(fs.readFileSync(record, 'utf8')) as { tmp: string; codexHome: string }; + expect(child.tmp).toBeTruthy(); + expect(path.relative(child.tmp, child.codexHome).startsWith('..')).toBe(true); + }); + } finally { + fs.rmSync(record, { force: true }); + } + }); + for (const exitCode of [2, 137]) { test(`retains process exit ${exitCode}`, async () => { await withFakeCodex(`process.exit(${exitCode});`, async ({ skillDir }) => { diff --git a/test/helpers/codex-session-runner.ts b/test/helpers/codex-session-runner.ts index b659a8fe4..4eea35931 100644 --- a/test/helpers/codex-session-runner.ts +++ b/test/helpers/codex-session-runner.ts @@ -270,12 +270,17 @@ export async function runCodexSkill(opts: { // itself; command subprocesses codex spawned survived as orphans holding // our pipes open (the same blocked-drain hang the claude runner fixed — // this copy never inherited that fix until now). + // Codex refuses to create its PATH aliases (codex-linux-sandbox among + // them) when CODEX_HOME sits under its temp dir, so the child gets a temp + // dir beside CODEX_HOME instead of the shard TMPDIR that holds tempHome. + const tempTmpDir = path.join(tempHome, 'tmp'); + fs.mkdirSync(tempTmpDir, { recursive: true }); const proc = spawn('codex', args, { cwd: cwd || skillDir, stdio: ['ignore', 'pipe', 'pipe'], detached: process.platform !== 'win32', env: hermeticChildEnv( - { HOME: tempHome, CODEX_HOME: tempCodexDir }, + { HOME: tempHome, CODEX_HOME: tempCodexDir, TMPDIR: tempTmpDir }, { extraAllow: ['OPENAI_API_KEY', 'CODEX_*'] }, ), });