mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-18 20:17:45 +00:00
Merge pull request #852 from kaitranntt/kai/fix/ccsx-codex-auth
fix(codex): harden native alias launches
This commit is contained in:
+47
@@ -95,6 +95,7 @@ interface RuntimeReasoningResolution {
|
||||
}
|
||||
|
||||
const CODEX_RUNTIME_REASONING_LEVELS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh']);
|
||||
const CODEX_NATIVE_PASSTHROUGH_FLAGS = new Set(['--help', '-h', '--version', '-v']);
|
||||
|
||||
/**
|
||||
* Smart profile detection
|
||||
@@ -239,6 +240,47 @@ function resolveNativeClaudeLaunchArgs(
|
||||
return buildOfficialChannelsArgs(args, plan.appliedChannels, plan.wantsPermissionBypass);
|
||||
}
|
||||
|
||||
function shouldPassthroughNativeCodexFlagCommand(args: string[]): boolean {
|
||||
const targetArgs = stripTargetFlag(args);
|
||||
if (targetArgs.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
resolveTargetType(args) === 'codex' && CODEX_NATIVE_PASSTHROUGH_FLAGS.has(targetArgs[0] || '')
|
||||
);
|
||||
}
|
||||
|
||||
function execNativeCodexFlagCommand(args: string[]): void {
|
||||
const adapter = getTarget('codex');
|
||||
if (!adapter) {
|
||||
console.error(fail('Target adapter not found for "codex"'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const binaryInfo = adapter.detectBinary();
|
||||
if (!binaryInfo) {
|
||||
console.error(fail('Codex CLI not found.'));
|
||||
console.error(info('Install a recent @openai/codex build, then retry.'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const targetArgs = stripTargetFlag(args);
|
||||
const creds: TargetCredentials = {
|
||||
profile: 'default',
|
||||
baseUrl: '',
|
||||
apiKey: '',
|
||||
};
|
||||
|
||||
const builtArgs = adapter.buildArgs('default', targetArgs, {
|
||||
creds,
|
||||
profileType: 'default',
|
||||
binaryInfo,
|
||||
});
|
||||
const targetEnv = adapter.buildEnv(creds, 'default');
|
||||
adapter.exec(builtArgs, targetEnv, { binaryInfo });
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
// Register target adapters
|
||||
registerTarget(new ClaudeAdapter());
|
||||
@@ -315,6 +357,11 @@ async function main(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldPassthroughNativeCodexFlagCommand(args)) {
|
||||
execNativeCodexFlagCommand(args);
|
||||
return;
|
||||
}
|
||||
|
||||
const firstArg = args[0];
|
||||
|
||||
// Trigger update check early for ALL commands except version/help/update
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as fs from 'fs';
|
||||
import type { ProfileType } from '../types/profile';
|
||||
import { runCleanup } from '../errors';
|
||||
import { wireChildProcessSignals } from '../utils/signal-forwarder';
|
||||
import { escapeShellArg, stripAnthropicEnv } from '../utils/shell-executor';
|
||||
import { escapeShellArg, stripAnthropicEnv, stripCodexSessionEnv } from '../utils/shell-executor';
|
||||
import type {
|
||||
TargetAdapter,
|
||||
TargetBinaryInfo,
|
||||
@@ -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<string>();
|
||||
|
||||
@@ -76,7 +88,7 @@ export class CodexAdapter implements TargetAdapter {
|
||||
readonly displayName = 'Codex CLI';
|
||||
|
||||
detectBinary(): TargetBinaryInfo | null {
|
||||
return getCodexBinaryInfo();
|
||||
return getCodexBinaryInfo({ includeVersion: false, includeFeatures: false });
|
||||
}
|
||||
|
||||
async prepareCredentials(_creds: TargetCredentials): Promise<void> {
|
||||
@@ -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()) {
|
||||
@@ -148,7 +160,9 @@ export class CodexAdapter implements TargetAdapter {
|
||||
}
|
||||
|
||||
buildEnv(creds: TargetCredentials, profileType: ProfileType): NodeJS.ProcessEnv {
|
||||
const env: NodeJS.ProcessEnv = { ...stripAnthropicEnv(process.env) };
|
||||
const env: NodeJS.ProcessEnv = {
|
||||
...stripCodexSessionEnv(stripAnthropicEnv(process.env)),
|
||||
};
|
||||
delete env[CODEX_RUNTIME_ENV_KEY];
|
||||
if (profileType !== 'default') {
|
||||
if (!creds.apiKey?.trim()) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -118,7 +118,10 @@ export function detectCodexCli(): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getCodexBinaryInfo(): TargetBinaryInfo | null {
|
||||
export function getCodexBinaryInfo(options?: {
|
||||
includeVersion?: boolean;
|
||||
includeFeatures?: boolean;
|
||||
}): TargetBinaryInfo | null {
|
||||
const codexPath = detectCodexCli();
|
||||
if (!codexPath) return null;
|
||||
|
||||
@@ -126,13 +129,21 @@ export function getCodexBinaryInfo(): TargetBinaryInfo | null {
|
||||
return {
|
||||
path: codexPath,
|
||||
needsShell: isWindows && /\.(cmd|bat|ps1)$/i.test(codexPath),
|
||||
version: readCodexVersion(codexPath),
|
||||
features: detectCodexFeatures(codexPath),
|
||||
version: options?.includeVersion === false ? undefined : readCodexVersion(codexPath),
|
||||
features: options?.includeFeatures === false ? undefined : detectCodexFeatures(codexPath),
|
||||
};
|
||||
}
|
||||
|
||||
export function codexBinarySupportsConfigOverrides(
|
||||
binaryInfo: TargetBinaryInfo | null | undefined
|
||||
): boolean {
|
||||
return Boolean(binaryInfo?.features?.includes(CODEX_CONFIG_OVERRIDE_FEATURE));
|
||||
if (!binaryInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (binaryInfo.features) {
|
||||
return binaryInfo.features.includes(CODEX_CONFIG_OVERRIDE_FEATURE);
|
||||
}
|
||||
|
||||
return detectCodexFeatures(binaryInfo.path).includes(CODEX_CONFIG_OVERRIDE_FEATURE);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,25 @@ export function stripClaudeCodeEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip Codex session-scoped env vars before launching a nested Codex process.
|
||||
*
|
||||
* Keep real user config such as CODEX_HOME intact. Only remove the known
|
||||
* session/runtime metadata exported by the current Codex host process.
|
||||
*/
|
||||
export function stripCodexSessionEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||
const sessionKeys = new Set(['CODEX_CI', 'CODEX_MANAGED_BY_BUN', 'CODEX_THREAD_ID']);
|
||||
const result: NodeJS.ProcessEnv = {};
|
||||
for (const key of Object.keys(env)) {
|
||||
const upperKey = key.toUpperCase();
|
||||
if (sessionKeys.has(upperKey)) {
|
||||
continue;
|
||||
}
|
||||
result[key] = env[key];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve CCS-managed environment overrides for Claude launch.
|
||||
* - preferences.auto_update: false -> DISABLE_AUTOUPDATER=1
|
||||
|
||||
@@ -165,24 +165,74 @@ describe('CodexAdapter', () => {
|
||||
});
|
||||
|
||||
test('injects CCS_CODEX_API_KEY for CCS-backed launches only', () => {
|
||||
const settingsEnv = adapter.buildEnv(
|
||||
{
|
||||
profile: 'codex',
|
||||
baseUrl: 'http://127.0.0.1:8317/api/provider/codex',
|
||||
apiKey: 'cliproxy-token',
|
||||
},
|
||||
'cliproxy'
|
||||
);
|
||||
expect(settingsEnv.CCS_CODEX_API_KEY).toBe('cliproxy-token');
|
||||
const originalCodeXHome = process.env.CODEX_HOME;
|
||||
const originalCodeXCi = process.env.CODEX_CI;
|
||||
const originalCodeXManagedByBun = process.env.CODEX_MANAGED_BY_BUN;
|
||||
const originalCodeXThreadId = process.env.CODEX_THREAD_ID;
|
||||
const originalAnthropicBaseUrl = process.env.ANTHROPIC_BASE_URL;
|
||||
|
||||
const defaultEnv = adapter.buildEnv(
|
||||
{
|
||||
profile: 'default',
|
||||
baseUrl: '',
|
||||
apiKey: '',
|
||||
},
|
||||
'default'
|
||||
);
|
||||
expect(defaultEnv.CCS_CODEX_API_KEY).toBeUndefined();
|
||||
try {
|
||||
process.env.CODEX_HOME = '/tmp/codex-home';
|
||||
process.env.CODEX_CI = '1';
|
||||
process.env.CODEX_MANAGED_BY_BUN = '1';
|
||||
process.env.CODEX_THREAD_ID = 'thread-123';
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://stale-proxy.invalid';
|
||||
|
||||
const settingsEnv = adapter.buildEnv(
|
||||
{
|
||||
profile: 'codex',
|
||||
baseUrl: 'http://127.0.0.1:8317/api/provider/codex',
|
||||
apiKey: 'cliproxy-token',
|
||||
},
|
||||
'cliproxy'
|
||||
);
|
||||
expect(settingsEnv.CCS_CODEX_API_KEY).toBe('cliproxy-token');
|
||||
expect(settingsEnv.CODEX_HOME).toBe('/tmp/codex-home');
|
||||
expect(settingsEnv.CODEX_CI).toBeUndefined();
|
||||
expect(settingsEnv.CODEX_MANAGED_BY_BUN).toBeUndefined();
|
||||
expect(settingsEnv.CODEX_THREAD_ID).toBeUndefined();
|
||||
expect(settingsEnv.ANTHROPIC_BASE_URL).toBeUndefined();
|
||||
|
||||
const defaultEnv = adapter.buildEnv(
|
||||
{
|
||||
profile: 'default',
|
||||
baseUrl: '',
|
||||
apiKey: '',
|
||||
},
|
||||
'default'
|
||||
);
|
||||
expect(defaultEnv.CCS_CODEX_API_KEY).toBeUndefined();
|
||||
expect(defaultEnv.CODEX_HOME).toBe('/tmp/codex-home');
|
||||
expect(defaultEnv.CODEX_CI).toBeUndefined();
|
||||
expect(defaultEnv.CODEX_MANAGED_BY_BUN).toBeUndefined();
|
||||
expect(defaultEnv.CODEX_THREAD_ID).toBeUndefined();
|
||||
expect(defaultEnv.ANTHROPIC_BASE_URL).toBeUndefined();
|
||||
} finally {
|
||||
if (originalCodeXHome === undefined) {
|
||||
delete process.env.CODEX_HOME;
|
||||
} else {
|
||||
process.env.CODEX_HOME = originalCodeXHome;
|
||||
}
|
||||
if (originalCodeXCi === undefined) {
|
||||
delete process.env.CODEX_CI;
|
||||
} else {
|
||||
process.env.CODEX_CI = originalCodeXCi;
|
||||
}
|
||||
if (originalCodeXManagedByBun === undefined) {
|
||||
delete process.env.CODEX_MANAGED_BY_BUN;
|
||||
} else {
|
||||
process.env.CODEX_MANAGED_BY_BUN = originalCodeXManagedByBun;
|
||||
}
|
||||
if (originalCodeXThreadId === undefined) {
|
||||
delete process.env.CODEX_THREAD_ID;
|
||||
} else {
|
||||
process.env.CODEX_THREAD_ID = originalCodeXThreadId;
|
||||
}
|
||||
if (originalAnthropicBaseUrl === undefined) {
|
||||
delete process.env.ANTHROPIC_BASE_URL;
|
||||
} else {
|
||||
process.env.ANTHROPIC_BASE_URL = originalAnthropicBaseUrl;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,6 +25,21 @@ function runCcs(args: string[], env: NodeJS.ProcessEnv): RunResult {
|
||||
};
|
||||
}
|
||||
|
||||
function runCodexAlias(args: string[], env: NodeJS.ProcessEnv): RunResult {
|
||||
const codexEntry = path.join(process.cwd(), 'src', 'bin', 'codex-runtime.ts');
|
||||
const result = spawnSync(process.execPath, [codexEntry, ...args], {
|
||||
encoding: 'utf8',
|
||||
env,
|
||||
timeout: 20000,
|
||||
});
|
||||
|
||||
return {
|
||||
status: result.status,
|
||||
stdout: result.stdout || '',
|
||||
stderr: result.stderr || '',
|
||||
};
|
||||
}
|
||||
|
||||
function readLoggedCodexCalls(logPath: string): string[][] {
|
||||
if (!fs.existsSync(logPath)) {
|
||||
return [];
|
||||
@@ -38,11 +53,25 @@ function readLoggedCodexCalls(logPath: string): string[][] {
|
||||
.map((line) => JSON.parse(line) as string[]);
|
||||
}
|
||||
|
||||
function readLoggedCodexEnv(logPath: string): Record<string, string | undefined>[] {
|
||||
if (!fs.existsSync(logPath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return fs
|
||||
.readFileSync(logPath, 'utf8')
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(Boolean)
|
||||
.map((line) => JSON.parse(line) as Record<string, string | undefined>);
|
||||
}
|
||||
|
||||
describe('codex runtime integration', () => {
|
||||
let tmpHome: string;
|
||||
let ccsDir: string;
|
||||
let fakeCodexPath: string;
|
||||
let codexArgsLogPath: string;
|
||||
let codexEnvLogPath: string;
|
||||
let emptyPathDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -54,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 });
|
||||
@@ -67,11 +97,24 @@ const out = process.env.CCS_TEST_CODEX_ARGS_OUT;
|
||||
if (out) {
|
||||
fs.appendFileSync(out, JSON.stringify(process.argv.slice(2)) + '\\n');
|
||||
}
|
||||
if (process.argv[2] === '--version') {
|
||||
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);
|
||||
}
|
||||
if (process.argv[2] === '--help') {
|
||||
if (process.argv[2] === '--help' || process.argv[2] === '-h') {
|
||||
process.stdout.write(
|
||||
process.env.CCS_TEST_CODEX_HELP ||
|
||||
' -c, --config <key=value>\\n -p, --profile <CONFIG_PROFILE>\\n'
|
||||
@@ -93,7 +136,7 @@ process.exit(0);
|
||||
fs.rmSync(tmpHome, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('ignores numeric CCS_THINKING env overrides for native Codex default mode', () => {
|
||||
it('does not preflight native Codex default launches when no runtime overrides are needed', () => {
|
||||
if (process.platform === 'win32') return;
|
||||
|
||||
const result = runCcs(['default', '--target', 'codex', 'fix failing tests'], {
|
||||
@@ -108,7 +151,7 @@ process.exit(0);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
const calls = readLoggedCodexCalls(codexArgsLogPath);
|
||||
expect(calls.at(-1)).toEqual(['fix failing tests']);
|
||||
expect(calls).toEqual([['fix failing tests']]);
|
||||
});
|
||||
|
||||
it('ignores off-style CCS_THINKING env overrides for native Codex default mode', () => {
|
||||
@@ -126,7 +169,79 @@ process.exit(0);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
const calls = readLoggedCodexCalls(codexArgsLogPath);
|
||||
expect(calls.at(-1)).toEqual(['fix failing tests']);
|
||||
expect(calls).toEqual([['fix failing tests']]);
|
||||
});
|
||||
|
||||
for (const versionFlag of ['--version', '-v']) {
|
||||
it(`passes ccsx ${versionFlag} straight through to the native Codex binary`, () => {
|
||||
if (process.platform === 'win32') return;
|
||||
|
||||
const result = runCodexAlias([versionFlag], {
|
||||
...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',
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain('codex-cli 9.9.9-test');
|
||||
expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([[versionFlag]]);
|
||||
});
|
||||
}
|
||||
|
||||
for (const helpFlag of ['--help', '-h']) {
|
||||
it(`passes ccsx ${helpFlag} straight through to the native Codex binary`, () => {
|
||||
if (process.platform === 'win32') return;
|
||||
|
||||
const result = runCodexAlias([helpFlag], {
|
||||
...process.env,
|
||||
CI: '1',
|
||||
NO_COLOR: '1',
|
||||
CCS_HOME: tmpHome,
|
||||
CCS_CODEX_PATH: fakeCodexPath,
|
||||
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
|
||||
CCS_TEST_CODEX_HELP: 'codex native help text',
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain('codex native help text');
|
||||
expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([[helpFlag]]);
|
||||
});
|
||||
}
|
||||
|
||||
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', () => {
|
||||
@@ -139,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 <CONFIG_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([['--version'], ['--help']]);
|
||||
expect(calls).toEqual([['--help'], ['--version']]);
|
||||
});
|
||||
|
||||
it('reports unsupported generic settings profiles before Codex install guidance', () => {
|
||||
|
||||
Reference in New Issue
Block a user