From 014a844844c76fe5dfce7763ed62e071ee063f58 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Tue, 31 Mar 2026 23:38:23 -0400 Subject: [PATCH] fix: probe Codex config override support directly --- src/targets/codex-detector.ts | 19 ++++++- tests/unit/targets/codex-detector.test.ts | 52 +++++++++++++++++++ .../targets/codex-runtime-integration.test.ts | 39 +++++++++++++- .../codex-settings-bridge-launch.test.ts | 7 +++ .../codex-dashboard-service.test.ts | 22 +++++++- 5 files changed, 134 insertions(+), 5 deletions(-) diff --git a/src/targets/codex-detector.ts b/src/targets/codex-detector.ts index faf52a35..31e72423 100644 --- a/src/targets/codex-detector.ts +++ b/src/targets/codex-detector.ts @@ -5,6 +5,7 @@ import { escapeShellArg } from '../utils/shell-executor'; import type { TargetBinaryInfo } from './target-adapter'; const CODEX_CONFIG_OVERRIDE_FEATURE = 'config-overrides'; +const CODEX_CONFIG_OVERRIDE_PROBE_ARGS = ['-c', 'model="gpt-5"', '--version']; function runCodexProbe(codexPath: string, args: string[]): string | undefined { const isWindows = process.platform === 'win32'; @@ -49,9 +50,25 @@ export function readCodexVersion(codexPath: string): string | undefined { return runCodexProbe(codexPath, ['--version'])?.trim(); } +function codexHelpAdvertisesConfigOverrides(helpText: string | undefined): boolean { + if (!helpText) { + return false; + } + + return /(^|\n)\s*-c,\s*--config\b/m.test(helpText) || helpText.includes('--config '); +} + +function codexSupportsConfigOverrideProbe(codexPath: string): boolean { + return !!runCodexProbe(codexPath, CODEX_CONFIG_OVERRIDE_PROBE_ARGS)?.trim(); +} + function detectCodexFeatures(codexPath: string): readonly string[] { + if (codexSupportsConfigOverrideProbe(codexPath)) { + return [CODEX_CONFIG_OVERRIDE_FEATURE]; + } + const helpText = runCodexProbe(codexPath, ['--help']); - return helpText?.includes('--config ') ? [CODEX_CONFIG_OVERRIDE_FEATURE] : []; + return codexHelpAdvertisesConfigOverrides(helpText) ? [CODEX_CONFIG_OVERRIDE_FEATURE] : []; } export function detectCodexCli(): string | null { diff --git a/tests/unit/targets/codex-detector.test.ts b/tests/unit/targets/codex-detector.test.ts index c392bcf7..69f71db1 100644 --- a/tests/unit/targets/codex-detector.test.ts +++ b/tests/unit/targets/codex-detector.test.ts @@ -72,4 +72,56 @@ describe('codex-detector', () => { execFileSyncSpy.mockRestore(); }); + + it('falls back to a direct -c probe when help text omits the config flag', () => { + const fakeCodex = path.join(tmpDir, 'codex'); + fs.writeFileSync(fakeCodex, ''); + process.env.CCS_CODEX_PATH = fakeCodex; + + const execFileSyncSpy = spyOn(childProcess, 'execFileSync').mockImplementation((command, args) => { + const joinedArgs = Array.isArray(args) ? args.join(' ') : ''; + + if (joinedArgs.includes('--help')) { + return 'Codex CLI\n'; + } + + if (joinedArgs.includes('-c') && joinedArgs.includes('--version')) { + return 'codex-cli 0.119.0-alpha.1'; + } + + return 'codex-cli 0.119.0-alpha.1'; + }); + + const info = getCodexBinaryInfo(); + + expect(info?.features).toContain('config-overrides'); + + execFileSyncSpy.mockRestore(); + }); + + it('still detects support from broader help text when the direct probe fails', () => { + const fakeCodex = path.join(tmpDir, 'codex'); + fs.writeFileSync(fakeCodex, ''); + process.env.CCS_CODEX_PATH = fakeCodex; + + const execFileSyncSpy = spyOn(childProcess, 'execFileSync').mockImplementation((command, args) => { + const joinedArgs = Array.isArray(args) ? args.join(' ') : ''; + + if (joinedArgs.includes('--help')) { + return 'Codex CLI\n -c, --config \n'; + } + + if (joinedArgs.includes('-c') && joinedArgs.includes('--version')) { + throw new Error('unsupported'); + } + + return 'codex-cli 0.119.0-alpha.1'; + }); + + const info = getCodexBinaryInfo(); + + expect(info?.features).toContain('config-overrides'); + + execFileSyncSpy.mockRestore(); + }); }); diff --git a/tests/unit/targets/codex-runtime-integration.test.ts b/tests/unit/targets/codex-runtime-integration.test.ts index 7e06621d..92539e48 100644 --- a/tests/unit/targets/codex-runtime-integration.test.ts +++ b/tests/unit/targets/codex-runtime-integration.test.ts @@ -126,7 +126,17 @@ if (envOut) { }) + '\\n' ); } -if (cliArgs.includes('--version') || cliArgs.includes('-v')) { +const configFlagIndex = cliArgs.findIndex((arg) => arg === '-c' || arg === '--config'); +if (process.env.CCS_TEST_CODEX_CONFIG_OVERRIDE_STATUS === 'unsupported' && configFlagIndex !== -1) { + process.stderr.write('codex: unknown option --config\\n'); + process.exit(1); +} +if ( + cliArgs.includes('--version') || + cliArgs.includes('-v') || + (configFlagIndex !== -1 && + (cliArgs[configFlagIndex + 2] === '--version' || cliArgs[configFlagIndex + 2] === '-v')) +) { process.stdout.write(process.env.CCS_TEST_CODEX_VERSION || 'codex-cli 0.118.0-alpha.3'); process.exit(0); } @@ -324,6 +334,7 @@ process.exit(0); CCS_HOME: tmpHome, CCS_CODEX_PATH: fakeCodexPath, CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath, + CCS_TEST_CODEX_CONFIG_OVERRIDE_STATUS: 'unsupported', CCS_TEST_CODEX_VERSION: 'codex-cli 9.9.9-test', CCS_TEST_CODEX_HELP: ' -p, --profile \\n', } @@ -333,7 +344,31 @@ process.exit(0); 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'], ['--version']]); + expect(calls).toEqual([['-c', 'model="gpt-5"', '--version'], ['--help'], ['--version']]); + }); + + it('accepts native Codex reasoning overrides when the direct -c probe succeeds', () => { + if (process.platform === 'win32') return; + + const result = runCcs( + ['default', '--target', 'codex', '--effort', 'high', 'fix failing tests'], + { + ...process.env, + CI: '1', + NO_COLOR: '1', + 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(0); + expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([ + ['-c', 'model="gpt-5"', '--version'], + ['-c', 'model_reasoning_effort="high"', 'fix failing tests'], + ]); }); it('reports unsupported generic settings profiles before Codex install guidance', () => { diff --git a/tests/unit/targets/codex-settings-bridge-launch.test.ts b/tests/unit/targets/codex-settings-bridge-launch.test.ts index 814d4ce5..9e520ae0 100644 --- a/tests/unit/targets/codex-settings-bridge-launch.test.ts +++ b/tests/unit/targets/codex-settings-bridge-launch.test.ts @@ -68,6 +68,13 @@ describe('Codex settings bridge launch', () => { fs.writeFileSync( fakeCodexPath, `#!/bin/sh +if [ "$1" = "-c" ] || [ "$1" = "--config" ]; then + if [ "$3" = "--version" ] || [ "$3" = "-v" ]; then + echo "codex-cli 0.118.0-alpha.3" + exit 0 + fi +fi + if [ "$1" = "--version" ]; then echo "codex-cli 0.118.0-alpha.3" exit 0 diff --git a/tests/unit/web-server/codex-dashboard-service.test.ts b/tests/unit/web-server/codex-dashboard-service.test.ts index 2700622b..7dc64be6 100644 --- a/tests/unit/web-server/codex-dashboard-service.test.ts +++ b/tests/unit/web-server/codex-dashboard-service.test.ts @@ -20,14 +20,29 @@ const testRoot = path.join(os.tmpdir(), `ccs-codex-dashboard-test-${Date.now()}` const codexHome = path.join(testRoot, '.codex-home'); const codexStubPath = path.join(testRoot, 'codex'); -function writeCodexStub(options?: { helpText?: string; version?: string }) { +function writeCodexStub(options?: { + helpText?: string; + version?: string; + supportsConfigOverrides?: boolean; +}) { const helpText = options?.helpText ?? ' -c, --config \n -p, --profile \n'; const version = options?.version ?? 'codex-cli 0.118.0-alpha.3'; + const supportsConfigOverrides = options?.supportsConfigOverrides ?? true; fs.writeFileSync( codexStubPath, `#!/bin/sh +if [ "$1" = "-c" ] || [ "$1" = "--config" ]; then + if [ "${supportsConfigOverrides ? '1' : '0'}" != "1" ]; then + printf '%s\\n' 'codex: unknown option --config' >&2 + exit 1 + fi + if [ "$3" = "--version" ] || [ "$3" = "-v" ]; then + printf '%s\\n' "${version}" + exit 0 + fi +fi if [ "$1" = "--version" ]; then printf '%s\\n' "${version}" exit 0 @@ -272,7 +287,10 @@ requires_openai_auth = true }); it('warns when active profile is missing, config overrides are unavailable, or risky fields exist', async () => { - writeCodexStub({ helpText: ' -p, --profile \n' }); + writeCodexStub({ + helpText: ' -p, --profile \n', + supportsConfigOverrides: false, + }); fs.writeFileSync( path.join(codexHome, 'config.toml'), `profile = "missing-profile"