fix(codex): pass native ccsx subcommands through (#1290)

This commit is contained in:
Kai (Tam Nhu) Tran
2026-05-18 10:48:44 -04:00
committed by GitHub
parent fa06cb2c92
commit 8a37578702
6 changed files with 117 additions and 13 deletions
+1 -1
View File
@@ -16,7 +16,7 @@
### Bug Fixes
* **codex:** Pass `ccsx resume` through to native Codex instead of treating `resume` as a CCS profile.
* **codex:** Pass native `ccsx` Codex subcommands such as `exec`, `apply`, `mcp`, `plugin`, `completion`, and `resume` plus upstream aliases such as `e` and `a` through before CCS profile detection while keeping CCS-owned commands reserved.
## [7.79.1](https://github.com/kaitranntt/ccs/compare/v7.79.0...v7.79.1) (2026-05-14)
+2 -2
View File
@@ -1,6 +1,6 @@
# CCS Codebase Summary
Last Updated: 2026-04-26
Last Updated: 2026-05-18
Comprehensive overview of the modularized CCS codebase structure following the Phase 9 modularization effort (Settings, Analytics, Auth Monitor splits + Test Infrastructure), v7.1 Remote CLIProxy feature, v7.2 Kiro + GitHub Copilot (ghcp) OAuth providers, v7.14 Hybrid Quota Management, v7.34 Image Analysis Hook, account-context validation hardening, Official Claude Channels runtime support, native Codex runtime target support, native Codex/Droid usage collectors, and models.dev-backed model pricing metadata.
@@ -273,7 +273,7 @@ src/
### Native Codex Runtime Target
- Dedicated runtime entrypoints: `ccs-codex` and `ccsx` resolve through `src/bin/codex-runtime.ts`, while `ccsxp` resolves through `src/bin/ccsxp-runtime.ts`; all three set `CCS_INTERNAL_ENTRY_TARGET=codex` before delegating to `src/targets/target-resolver.ts`.
- Native Codex passthrough: `ccsx --help`, `ccsx --version`, and `ccsx resume ...` short-circuit before CCS profile detection, so Codex diagnostics and session resume behavior stay aligned with the upstream Codex CLI. CCS-owned `ccsx auth` remains the managed Codex profile namespace.
- Native Codex passthrough: `ccsx --help`, `ccsx --version`, and known upstream Codex subcommands such as `ccsx exec ...`, `ccsx apply ...`, `ccsx mcp ...`, `ccsx plugin ...`, `ccsx completion ...`, and `ccsx resume ...` short-circuit before CCS profile detection; upstream aliases such as `ccsx e ...` and `ccsx a ...` are included. CCS-owned `ccsx auth`, `ccsx doctor`, and `ccsx update` remain reserved for CCS.
- Provider shortcut behavior: `ccsxp` strips user-supplied `--target` overrides and prepends `--config model_provider="cliproxy"` so it behaves like native Codex plus the CLIProxy provider recipe. The stricter CCS-managed bridge remains available explicitly through `ccs codex --target codex`. It pins `CODEX_HOME` to native `~/.codex` by default so inherited launcher state does not send history/config writes to a nonstandard Codex root; `CCSXP_CODEX_HOME` is the explicit override. On launch, CCS repairs the native `[model_providers.cliproxy]` stanza in `config.toml`, preserves a valid custom `base_url`, reads that provider's configured `env_key` (default `CLIPROXY_API_KEY`), and injects the effective CLIProxy auth token into that key for the child Codex process.
- Implicit Codex launches such as `ccs --target codex` and `ccsxp` use native Codex default mode even when the CCS default profile is a Claude account. Explicit unsupported profiles such as `ccs work --target codex` still fail fast with native-vs-pool guidance.
- `argv[0]` alias mapping still exists in `src/targets/target-resolver.ts` for same-binary/custom alias scenarios, but the built-in npm bins above do not depend on that map at runtime.
+3 -2
View File
@@ -1,6 +1,6 @@
# Target Adapters
Last Updated: 2026-03-28
Last Updated: 2026-05-18
Detailed documentation of the target adapter pattern and implementations.
@@ -515,7 +515,8 @@ ccs-codex
ccsx
→ dist/bin/codex-runtime.js
CCS_INTERNAL_ENTRY_TARGET=codex
→ passes native Codex diagnostics and `resume` through before CCS profile detection
→ passes native Codex diagnostics plus known upstream Codex subcommands and aliases through before CCS profile detection
→ reserves CCS-owned `auth`, `doctor`, and `update`
ccsxp
→ dist/bin/ccsxp-runtime.js
@@ -9,7 +9,37 @@ import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from 'bun:te
import * as os from 'os';
import * as fs from 'fs';
import * as path from 'path';
import { bootstrapAndParseEarlyCli } from '../cli-argument-parser';
import {
bootstrapAndParseEarlyCli,
CODEX_NATIVE_PASSTHROUGH_SUBCOMMANDS,
getNativeCodexPassthroughArgs,
} from '../cli-argument-parser';
import { setGlobalConfigDir } from '../../utils/config-manager';
const EXPECTED_NATIVE_CODEX_SUBCOMMANDS = [
'a',
'app',
'app-server',
'apply',
'cloud',
'completion',
'debug',
'e',
'exec',
'exec-server',
'features',
'fork',
'help',
'login',
'logout',
'mcp',
'mcp-server',
'plugin',
'remote-control',
'resume',
'review',
'sandbox',
];
// ========== Helpers ==========
@@ -53,6 +83,7 @@ describe('bootstrapAndParseEarlyCli', () => {
configurable: true,
});
delete process.env['CI'];
setGlobalConfigDir(undefined);
exitSpy.mockRestore();
});
@@ -150,3 +181,30 @@ describe('bootstrapAndParseEarlyCli', () => {
expect(result.browserLaunchOverride).toBeUndefined();
});
});
describe('native Codex passthrough parsing', () => {
it('keeps the native Codex passthrough subcommand list explicit', () => {
expect([...CODEX_NATIVE_PASSTHROUGH_SUBCOMMANDS].sort()).toEqual(
EXPECTED_NATIVE_CODEX_SUBCOMMANDS
);
for (const subcommand of EXPECTED_NATIVE_CODEX_SUBCOMMANDS) {
expect(getNativeCodexPassthroughArgs(['--target', 'codex', subcommand, '--help'])).toEqual([
subcommand,
'--help',
]);
expect(
getNativeCodexPassthroughArgs(['--target', 'codex', 'codex', subcommand, '--help'])
).toEqual([subcommand, '--help']);
}
});
it('keeps CCS-owned codex runtime commands out of native passthrough', () => {
for (const subcommand of ['auth', 'doctor', 'update']) {
expect(getNativeCodexPassthroughArgs(['--target', 'codex', subcommand, '--help'])).toBeNull();
expect(
getNativeCodexPassthroughArgs(['--target', 'codex', 'codex', subcommand, '--help'])
).toBeNull();
}
});
});
+24 -1
View File
@@ -175,7 +175,30 @@ export const CODEX_RUNTIME_REASONING_LEVELS = new Set([
]);
export const CODEX_NATIVE_PASSTHROUGH_FLAGS = new Set(['--help', '-h', '--version', '-v']);
export const CODEX_NATIVE_PASSTHROUGH_SUBCOMMANDS = new Set(['resume']);
export const CODEX_NATIVE_PASSTHROUGH_SUBCOMMANDS = new Set([
'a',
'app',
'app-server',
'apply',
'cloud',
'completion',
'debug',
'e',
'exec',
'exec-server',
'features',
'fork',
'help',
'login',
'logout',
'mcp',
'mcp-server',
'plugin',
'remote-control',
'resume',
'review',
'sandbox',
]);
export const NATIVE_CLAUDE_EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max'] as const;
@@ -498,10 +498,32 @@ process.exit(0);
});
}
it('passes ccsx resume straight through to the native Codex binary', () => {
for (const subcommand of ['exec', 'e', 'apply', 'a', 'mcp', 'plugin', 'completion', 'resume']) {
it(`passes ccsx ${subcommand} straight through to the native Codex binary`, () => {
if (process.platform === 'win32') return;
const result = runCodexAlias([subcommand, '--help'], {
...process.env,
CI: '1',
NO_COLOR: '1',
CCS_HOME: tmpHome,
CODEX_HOME: path.join(tmpHome, '.codex'),
CCS_CODEX_PATH: fakeCodexPath,
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
CCS_TEST_CODEX_HELP: `Native ${subcommand} help`,
});
expect(result.status).toBe(0);
expect(result.stdout).toContain(`Native ${subcommand} help`);
expect(result.stderr).not.toContain(`Profile not found: ${subcommand}`);
expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([[subcommand, '--help']]);
});
}
it('passes nested ccsx codex subcommands through to the native Codex binary', () => {
if (process.platform === 'win32') return;
const result = runCodexAlias(['resume', '--help'], {
const result = runCodexAlias(['codex', 'exec', '--help'], {
...process.env,
CI: '1',
NO_COLOR: '1',
@@ -509,13 +531,13 @@ process.exit(0);
CODEX_HOME: path.join(tmpHome, '.codex'),
CCS_CODEX_PATH: fakeCodexPath,
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
CCS_TEST_CODEX_HELP: 'Resume a previous interactive session',
CCS_TEST_CODEX_HELP: 'Native exec help',
});
expect(result.status).toBe(0);
expect(result.stdout).toContain('Resume a previous interactive session');
expect(result.stderr).not.toContain('Profile not found: resume');
expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([['resume', '--help']]);
expect(result.stdout).toContain('Native exec help');
expect(result.stderr).not.toContain('Profile not found: codex');
expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([['exec', '--help']]);
});
it('strips nested Codex session env from passthrough launches while keeping CODEX_HOME', () => {