diff --git a/src/bin/ccsxp-runtime.ts b/src/bin/ccsxp-runtime.ts index f488e5fb..429f8806 100644 --- a/src/bin/ccsxp-runtime.ts +++ b/src/bin/ccsxp-runtime.ts @@ -1,8 +1,22 @@ +const os = require('os'); +const path = require('path'); const { stripTargetFlag } = require('../targets/target-resolver'); +const { expandPath } = require('../utils/helpers'); const { fail } = require('../utils/ui'); process.env.CCS_INTERNAL_ENTRY_TARGET = 'codex'; +function resolveCcsxpCodexHome() { + const configuredHome = process.env.CCSXP_CODEX_HOME?.trim(); + if (configuredHome) { + return path.resolve(expandPath(configuredHome)); + } + + return path.join(os.homedir(), '.codex'); +} + +process.env.CODEX_HOME = resolveCcsxpCodexHome(); + // ccsxp is an opinionated shortcut for the built-in Codex-on-Codex route. // Strip user-supplied target overrides before forcing the shortcut target. const forwardedArgs = (() => { diff --git a/tests/unit/bin/ccsxp-runtime.test.ts b/tests/unit/bin/ccsxp-runtime.test.ts index ee4d0f40..2698007a 100644 --- a/tests/unit/bin/ccsxp-runtime.test.ts +++ b/tests/unit/bin/ccsxp-runtime.test.ts @@ -1,4 +1,6 @@ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; +import * as os from 'os'; +import * as path from 'path'; const wrapperPath = require.resolve('../../../src/bin/ccsxp-runtime.ts'); const ccsPath = require.resolve('../../../src/ccs.ts'); @@ -6,6 +8,8 @@ const ccsPath = require.resolve('../../../src/ccs.ts'); describe('ccsxp runtime wrapper', () => { const originalArgv = process.argv; const originalEntryTarget = process.env.CCS_INTERNAL_ENTRY_TARGET; + const originalCodexHome = process.env.CODEX_HOME; + const originalCcsxpCodexHome = process.env.CCSXP_CODEX_HOME; beforeEach(() => { delete require.cache[wrapperPath]; @@ -20,6 +24,16 @@ describe('ccsxp runtime wrapper', () => { } else { process.env.CCS_INTERNAL_ENTRY_TARGET = originalEntryTarget; } + if (originalCodexHome === undefined) { + delete process.env.CODEX_HOME; + } else { + process.env.CODEX_HOME = originalCodexHome; + } + if (originalCcsxpCodexHome === undefined) { + delete process.env.CCSXP_CODEX_HOME; + } else { + process.env.CCSXP_CODEX_HOME = originalCcsxpCodexHome; + } delete require.cache[wrapperPath]; delete require.cache[ccsPath]; @@ -32,9 +46,31 @@ describe('ccsxp runtime wrapper', () => { require(wrapperPath); expect(process.env.CCS_INTERNAL_ENTRY_TARGET).toBe('codex'); + expect(process.env.CODEX_HOME).toBe(path.join(os.homedir(), '.codex')); expect(process.argv.slice(2)).toEqual(['codex', '--target', 'codex', 'fix failing tests']); }); + it('pins ccsxp history to native Codex default instead of inherited CODEX_HOME', () => { + process.env.CODEX_HOME = '/tmp/inherited-managed-codex-home'; + process.argv = ['node', wrapperPath, '--version']; + require.cache[ccsPath] = { exports: {} } as NodeJS.Module; + + require(wrapperPath); + + expect(process.env.CODEX_HOME).toBe(path.join(os.homedir(), '.codex')); + }); + + it('allows an explicit ccsxp Codex home override', () => { + process.env.CODEX_HOME = '/tmp/inherited-managed-codex-home'; + process.env.CCSXP_CODEX_HOME = '/tmp/explicit-ccsxp-codex-home'; + process.argv = ['node', wrapperPath, '--version']; + require.cache[ccsPath] = { exports: {} } as NodeJS.Module; + + require(wrapperPath); + + expect(process.env.CODEX_HOME).toBe('/tmp/explicit-ccsxp-codex-home'); + }); + it('keeps flag-only invocations routed through the built-in codex profile shortcut', () => { process.argv = ['node', wrapperPath, '--version']; require.cache[ccsPath] = { exports: {} } as NodeJS.Module; diff --git a/tests/unit/targets/codex-runtime-integration.test.ts b/tests/unit/targets/codex-runtime-integration.test.ts index 9d79ebce..2006d868 100644 --- a/tests/unit/targets/codex-runtime-integration.test.ts +++ b/tests/unit/targets/codex-runtime-integration.test.ts @@ -377,10 +377,7 @@ process.exit(0); expect(result.status).toBe(0); const calls = readLoggedCodexCalls(codexArgsLogPath); expect(calls[1]).toEqual( - expect.arrayContaining([ - 'mcp_servers.ccs_browser.enabled=true', - 'fix failing tests', - ]) + expect.arrayContaining(['mcp_servers.ccs_browser.enabled=true', 'fix failing tests']) ); } finally { if (originalCcsHome !== undefined) { @@ -523,28 +520,27 @@ process.exit(0); if (process.platform === 'win32') return; const freshCodexHome = path.join(tmpHome, 'fresh-codex-home'); - 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_ENV_OUT: codexEnvLogPath, - CCS_TEST_CODEX_VERSION: 'codex-cli 9.9.9-test', - CODEX_HOME: freshCodexHome, - }); + 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_ENV_OUT: codexEnvLogPath, + CCS_TEST_CODEX_VERSION: 'codex-cli 9.9.9-test', + CODEX_HOME: freshCodexHome, + } + ); expect(result.status).toBe(0); expect(fs.existsSync(freshCodexHome)).toBe(true); expect(fs.statSync(freshCodexHome).isDirectory()).toBe(true); expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([ ['-c', 'model="gpt-5"', '--version'], - [ - '-c', - 'model_reasoning_effort="high"', - 'fix failing tests', - ], + ['-c', 'model_reasoning_effort="high"', 'fix failing tests'], ]); const loggedEnv = readLoggedCodexEnv(codexEnvLogPath); expect(loggedEnv).toHaveLength(2); @@ -570,13 +566,13 @@ process.exit(0); 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, - CODEX_HOME: invalidCodexHome, + ...process.env, + CI: '1', + NO_COLOR: '1', + CCS_HOME: tmpHome, + CCS_CODEX_PATH: fakeCodexPath, + CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath, + CODEX_HOME: invalidCodexHome, } ); @@ -661,6 +657,67 @@ process.exit(0); expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([['--version']]); }); + it('pins ccsxp Codex history to native default instead of inherited CODEX_HOME', () => { + if (process.platform === 'win32') return; + + const inheritedCodexHome = path.join(tmpHome, 'inherited-codex-home'); + const result = runCcsxpAlias(['--version'], { + ...process.env, + CI: '1', + NO_COLOR: '1', + CCS_HOME: tmpHome, + CCS_CODEX_PATH: fakeCodexPath, + CCS_TEST_CODEX_ENV_OUT: codexEnvLogPath, + CCS_TEST_CODEX_VERSION: 'codex-cli 9.9.9-test', + CODEX_HOME: inheritedCodexHome, + }); + + expect(result.status).toBe(0); + expect(readLoggedCodexEnv(codexEnvLogPath)).toEqual([ + { + CODEX_HOME: path.join(os.homedir(), '.codex'), + CODEX_CI: undefined, + CODEX_MANAGED_BY_BUN: undefined, + CODEX_THREAD_ID: undefined, + ANTHROPIC_BASE_URL: undefined, + CCS_BROWSER_USER_DATA_DIR: undefined, + CCS_BROWSER_PROFILE_DIR: undefined, + CCS_BROWSER_DEVTOOLS_WS_URL: undefined, + }, + ]); + }); + + it('honors CCSXP_CODEX_HOME for intentionally separate ccsxp history', () => { + if (process.platform === 'win32') return; + + const explicitCodexHome = path.join(tmpHome, 'explicit-ccsxp-codex-home'); + const result = runCcsxpAlias(['--version'], { + ...process.env, + CI: '1', + NO_COLOR: '1', + CCS_HOME: tmpHome, + CCS_CODEX_PATH: fakeCodexPath, + CCS_TEST_CODEX_ENV_OUT: codexEnvLogPath, + CCS_TEST_CODEX_VERSION: 'codex-cli 9.9.9-test', + CODEX_HOME: path.join(tmpHome, 'inherited-codex-home'), + CCSXP_CODEX_HOME: explicitCodexHome, + }); + + expect(result.status).toBe(0); + expect(readLoggedCodexEnv(codexEnvLogPath)).toEqual([ + { + CODEX_HOME: explicitCodexHome, + CODEX_CI: undefined, + CODEX_MANAGED_BY_BUN: undefined, + CODEX_THREAD_ID: undefined, + ANTHROPIC_BASE_URL: undefined, + CCS_BROWSER_USER_DATA_DIR: undefined, + CCS_BROWSER_PROFILE_DIR: undefined, + CCS_BROWSER_DEVTOOLS_WS_URL: undefined, + }, + ]); + }); + it('fails with a clean CLI error when ccsxp receives a malformed --target flag', () => { if (process.platform === 'win32') return; @@ -740,11 +797,7 @@ process.exit(0); expect(result.status).toBe(0); expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([ ['-c', 'model="gpt-5"', '--version'], - [ - '-c', - 'model_reasoning_effort="high"', - 'fix failing tests', - ], + ['-c', 'model_reasoning_effort="high"', 'fix failing tests'], ]); });