mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 08:17:11 +00:00
fix: resolve ccsx auth profiles before CCS profiles
This commit is contained in:
@@ -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 |
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user