fix: resolve ccsx auth profiles before CCS profiles

This commit is contained in:
Tam Nhu Tran
2026-05-22 15:54:55 -04:00
parent 921657097c
commit f667628411
4 changed files with 122 additions and 12 deletions
+5
View File
@@ -28,6 +28,9 @@ codex
# Terminal B:
eval "$(ccsx auth use personal)"
codex
# Or launch a named profile directly through ccsx
ccsx work
```
## Two-terminal example
@@ -49,6 +52,7 @@ codex # runs with CODEX_HOME=~/.ccs/codex-instances/pers
| Command | Description |
|---------|-------------|
| `ccsx auth create <name>` | Create profile dir + auto-login |
| `ccsx <name>` | Launch a named Codex auth profile |
| `ccsx auth login <name>` | (Re-)authenticate an existing profile |
| `ccsx auth switch <name>` | Set the persistent default profile for future `ccsx` launches |
| `ccsx auth use <name>` | Emit shell exports for this shell only (use with `eval`) |
@@ -60,6 +64,7 @@ codex # runs with CODEX_HOME=~/.ccs/codex-instances/pers
| Method | Scope | How |
|--------|-------|-----|
| `ccsx <name>` | One launch | Resolves `<name>` from the Codex profile registry |
| `ccsx auth switch <name>` | Future `ccsx` launches | Writes to `~/.ccs/codex-profiles.yaml` |
| `eval "$(ccsx auth use <name>)"` | Current shell only | Sets `CODEX_HOME` + `CCS_CODEX_PROFILE` in your shell |
+39 -12
View File
@@ -35,6 +35,31 @@ function errorMessage(err: unknown): string {
return String(err);
}
function maybeSelectPositionalCodexProfile(argv: string[]): void {
const candidate = (argv[2] ?? '').trim();
if (!candidate || candidate.startsWith('-') || candidate === 'auth') {
return;
}
const { getNativeCodexPassthroughArgs } = require('../dispatcher/cli-argument-parser') as {
getNativeCodexPassthroughArgs: (args: string[]) => string[] | null;
};
if (getNativeCodexPassthroughArgs(argv.slice(2)) !== null) {
return;
}
const { CodexProfileRegistry } = require('../codex-auth/codex-profile-registry') as {
CodexProfileRegistry: new () => { hasProfile: (name: string) => boolean };
};
const registry = new CodexProfileRegistry();
if (!registry.hasProfile(candidate)) {
return;
}
process.env.CCS_CODEX_PROFILE = candidate;
argv[2] = 'default';
}
/**
* Main entry-point for the ccsx / codex-runtime binary.
*
@@ -54,11 +79,13 @@ export async function main(argv: string[]): Promise<number> {
// ── non-auth branch: profile resolution ─────────────────────────────────
// F1: respect explicit CODEX_HOME unless CCS_CODEX_PROFILE asks for a managed profile.
const explicit = (process.env.CODEX_HOME ?? '').trim();
const profileOverride = (process.env.CCS_CODEX_PROFILE ?? '').trim();
if (!explicit || profileOverride) {
try {
try {
maybeSelectPositionalCodexProfile(argv);
// F1: respect explicit CODEX_HOME unless CCS_CODEX_PROFILE asks for a managed profile.
const explicit = (process.env.CODEX_HOME ?? '').trim();
const profileOverride = (process.env.CCS_CODEX_PROFILE ?? '').trim();
if (!explicit || profileOverride) {
const { resolveActiveProfile } = require('../codex-auth/resolve-active-profile') as {
resolveActiveProfile: (
env: NodeJS.ProcessEnv
@@ -85,15 +112,15 @@ export async function main(argv: string[]): Promise<number> {
);
}
}
} catch (resolverErr) {
const msg = errorMessage(resolverErr);
if (isCodexAuthProfileResolutionError(resolverErr)) {
process.stderr.write(`[X] codex-auth: ${msg}\n`);
return 1;
}
process.stderr.write(`[X] codex-auth: profile resolution failed (${msg})\n`);
}
} catch (resolverErr) {
const msg = errorMessage(resolverErr);
if (isCodexAuthProfileResolutionError(resolverErr)) {
process.stderr.write(`[X] codex-auth: ${msg}\n`);
return 1;
}
process.stderr.write(`[X] codex-auth: profile resolution failed (${msg})\n`);
return 1;
}
// ── delegate to CCS ─────────────────────────────────────────────────────
@@ -146,6 +146,28 @@ describe('codex-runtime router — non-auth profile resolution', () => {
expect(process.env.CODEX_HOME).toBe(profileDir);
});
it('resolves a first positional ccsx arg from the Codex auth registry before CCS profiles', async () => {
const profileDir = makeProfileDir('ck');
writeRegistry({
version: '1.0',
default: null,
profiles: { ck: { type: 'codex', created: '2026-01-01T00:00:00.000Z', last_used: null } },
});
require.cache[ccsPath] = { exports: {} } as NodeJS.Module;
flushRouterCache();
require.cache[ccsPath] = { exports: {} } as NodeJS.Module;
const argv = ['node', 'codex-runtime', 'ck', 'fix failing tests'];
const { main } = require(routerPath) as { main: (argv: string[]) => Promise<number> };
const code = await main(argv);
expect(code).toBe(-1);
expect(process.env.CCS_CODEX_PROFILE).toBe('ck');
expect(process.env.CODEX_HOME).toBe(profileDir);
expect(argv).toEqual(['node', 'codex-runtime', 'default', 'fix failing tests']);
});
it('leaves CODEX_HOME unset when no registry exists and no env profile set', async () => {
// No registry file, no CCS_CODEX_PROFILE
require.cache[ccsPath] = { exports: {} } as NodeJS.Module;
@@ -1095,6 +1095,62 @@ supports_websockets = false
expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([]);
});
it('launches a ccsx auth profile even when a Claude account has the same name', () => {
if (process.platform === 'win32') return;
const codexProfileDir = path.join(ccsDir, 'codex-instances', 'ck');
fs.mkdirSync(codexProfileDir, { recursive: true });
fs.writeFileSync(
path.join(ccsDir, 'config.yaml'),
[
'version: 2',
'accounts:',
' ck:',
' created: "2026-01-01"',
' last_used: "2026-01-01"',
].join('\n')
);
fs.writeFileSync(
path.join(ccsDir, 'codex-profiles.yaml'),
[
'version: "1.0"',
'default: null',
'profiles:',
' ck:',
' type: codex',
' created: "2026-01-01T00:00:00.000Z"',
' last_used: null',
].join('\n')
);
const result = runCodexAlias(['ck', 'fix failing tests'], {
...process.env,
CI: '1',
NO_COLOR: '1',
HOME: tmpHome,
CCS_HOME: tmpHome,
CCS_CODEX_PATH: fakeCodexPath,
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
CCS_TEST_CODEX_ENV_OUT: codexEnvLogPath,
});
expect(result.status).toBe(0);
expect(result.stderr).not.toContain('Codex CLI does not support Claude account-based profiles.');
expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([['fix failing tests']]);
expect(readLoggedCodexEnv(codexEnvLogPath)).toEqual([
{
CODEX_HOME: codexProfileDir,
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('rejects conflicting native provider config overrides for ccsxp', () => {
if (process.platform === 'win32') return;