diff --git a/src/targets/codex-adapter.ts b/src/targets/codex-adapter.ts index b2e6f774..d4491288 100644 --- a/src/targets/codex-adapter.ts +++ b/src/targets/codex-adapter.ts @@ -14,6 +14,7 @@ import { codexBinarySupportsConfigOverrides, detectCodexCli, getCodexBinaryInfo, + readCodexVersion, } from './codex-detector'; const CODEX_RUNTIME_PROVIDER_ID = 'ccs_runtime'; @@ -35,6 +36,17 @@ function buildConfigOverrideSupportError(binaryInfo?: TargetBinaryInfo): Error { ); } +function hydrateCodexBinaryVersion(binaryInfo?: TargetBinaryInfo): TargetBinaryInfo | undefined { + if (!binaryInfo || binaryInfo.version || !binaryInfo.path) { + return binaryInfo; + } + + return { + ...binaryInfo, + version: readCodexVersion(binaryInfo.path), + }; +} + function findDisallowedCodexManagedFlags(args: string[]): string[] { const disallowed = new Set(); @@ -99,7 +111,7 @@ export class CodexAdapter implements TargetAdapter { if (profileType === 'default') { if (reasoningOverride) { if (!codexBinarySupportsConfigOverrides(options?.binaryInfo)) { - throw buildConfigOverrideSupportError(options?.binaryInfo); + throw buildConfigOverrideSupportError(hydrateCodexBinaryVersion(options?.binaryInfo)); } return [ ...buildConfigOverrideArgs([ @@ -112,7 +124,7 @@ export class CodexAdapter implements TargetAdapter { } if (!codexBinarySupportsConfigOverrides(options?.binaryInfo)) { - throw buildConfigOverrideSupportError(options?.binaryInfo); + throw buildConfigOverrideSupportError(hydrateCodexBinaryVersion(options?.binaryInfo)); } if (!creds?.baseUrl?.trim() || !creds.apiKey?.trim()) { diff --git a/src/targets/codex-detector.ts b/src/targets/codex-detector.ts index 56cfb79b..faf52a35 100644 --- a/src/targets/codex-detector.ts +++ b/src/targets/codex-detector.ts @@ -45,7 +45,7 @@ function runCodexProbe(codexPath: string, args: string[]): string | undefined { } } -function readCodexVersion(codexPath: string): string | undefined { +export function readCodexVersion(codexPath: string): string | undefined { return runCodexProbe(codexPath, ['--version'])?.trim(); } diff --git a/tests/unit/targets/codex-runtime-integration.test.ts b/tests/unit/targets/codex-runtime-integration.test.ts index 50c2000c..161348fd 100644 --- a/tests/unit/targets/codex-runtime-integration.test.ts +++ b/tests/unit/targets/codex-runtime-integration.test.ts @@ -53,11 +53,25 @@ function readLoggedCodexCalls(logPath: string): string[][] { .map((line) => JSON.parse(line) as string[]); } +function readLoggedCodexEnv(logPath: string): Record[] { + if (!fs.existsSync(logPath)) { + return []; + } + + return fs + .readFileSync(logPath, 'utf8') + .trim() + .split('\n') + .filter(Boolean) + .map((line) => JSON.parse(line) as Record); +} + describe('codex runtime integration', () => { let tmpHome: string; let ccsDir: string; let fakeCodexPath: string; let codexArgsLogPath: string; + let codexEnvLogPath: string; let emptyPathDir: string; beforeEach(() => { @@ -69,6 +83,7 @@ describe('codex runtime integration', () => { ccsDir = path.join(tmpHome, '.ccs'); fakeCodexPath = path.join(tmpHome, 'fake-codex.js'); codexArgsLogPath = path.join(tmpHome, 'codex-args.log'); + codexEnvLogPath = path.join(tmpHome, 'codex-env.log'); emptyPathDir = path.join(tmpHome, 'empty-bin'); fs.mkdirSync(ccsDir, { recursive: true }); @@ -82,6 +97,19 @@ const out = process.env.CCS_TEST_CODEX_ARGS_OUT; if (out) { fs.appendFileSync(out, JSON.stringify(process.argv.slice(2)) + '\\n'); } +const envOut = process.env.CCS_TEST_CODEX_ENV_OUT; +if (envOut) { + fs.appendFileSync( + envOut, + JSON.stringify({ + CODEX_HOME: process.env.CODEX_HOME, + CODEX_CI: process.env.CODEX_CI, + CODEX_MANAGED_BY_BUN: process.env.CODEX_MANAGED_BY_BUN, + CODEX_THREAD_ID: process.env.CODEX_THREAD_ID, + ANTHROPIC_BASE_URL: process.env.ANTHROPIC_BASE_URL, + }) + '\\n' + ); +} if (process.argv[2] === '--version' || process.argv[2] === '-v') { process.stdout.write(process.env.CCS_TEST_CODEX_VERSION || 'codex-cli 0.118.0-alpha.3'); process.exit(0); @@ -184,6 +212,38 @@ process.exit(0); }); } + it('strips nested Codex session env from passthrough launches while keeping CODEX_HOME', () => { + if (process.platform === 'win32') return; + + const result = runCodexAlias(['--version'], { + ...process.env, + CI: '1', + NO_COLOR: '1', + CCS_HOME: tmpHome, + CCS_CODEX_PATH: fakeCodexPath, + CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath, + CCS_TEST_CODEX_ENV_OUT: codexEnvLogPath, + CCS_TEST_CODEX_VERSION: 'codex-cli 9.9.9-test', + CODEX_HOME: '/tmp/codex-home', + CODEX_CI: '1', + CODEX_MANAGED_BY_BUN: '1', + CODEX_THREAD_ID: 'thread-123', + ANTHROPIC_BASE_URL: 'https://stale-proxy.invalid', + }); + + expect(result.status).toBe(0); + expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([['--version']]); + expect(readLoggedCodexEnv(codexEnvLogPath)).toEqual([ + { + CODEX_HOME: '/tmp/codex-home', + CODEX_CI: undefined, + CODEX_MANAGED_BY_BUN: undefined, + CODEX_THREAD_ID: undefined, + ANTHROPIC_BASE_URL: undefined, + }, + ]); + }); + it('fails fast when native Codex reasoning overrides need unsupported --config support', () => { if (process.platform === 'win32') return; @@ -194,13 +254,15 @@ process.exit(0); CCS_HOME: tmpHome, CCS_CODEX_PATH: fakeCodexPath, CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath, + CCS_TEST_CODEX_VERSION: 'codex-cli 9.9.9-test', CCS_TEST_CODEX_HELP: ' -p, --profile \\n', }); expect(result.status).toBe(1); + expect(result.stderr).toContain('Codex CLI (codex-cli 9.9.9-test)'); expect(result.stderr).toContain('does not advertise --config overrides'); const calls = readLoggedCodexCalls(codexArgsLogPath); - expect(calls).toEqual([['--help']]); + expect(calls).toEqual([['--help'], ['--version']]); }); it('reports unsupported generic settings profiles before Codex install guidance', () => {