fix: probe Codex config override support directly

This commit is contained in:
Tam Nhu Tran
2026-03-31 23:38:23 -04:00
parent 8237c38c99
commit 014a844844
5 changed files with 134 additions and 5 deletions
+18 -1
View File
@@ -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 <key=value>');
}
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 <key=value>') ? [CODEX_CONFIG_OVERRIDE_FEATURE] : [];
return codexHelpAdvertisesConfigOverrides(helpText) ? [CODEX_CONFIG_OVERRIDE_FEATURE] : [];
}
export function detectCodexCli(): string | null {
+52
View File
@@ -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 <CONFIG_OVERRIDE>\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();
});
});
@@ -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 <CONFIG_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 <CONFIG_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', () => {
@@ -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
@@ -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 <key=value>\n -p, --profile <CONFIG_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 <CONFIG_PROFILE>\n' });
writeCodexStub({
helpText: ' -p, --profile <CONFIG_PROFILE>\n',
supportsConfigOverrides: false,
});
fs.writeFileSync(
path.join(codexHome, 'config.toml'),
`profile = "missing-profile"