From a39ded0b1a0e589046db204030bf171d94fb1ce7 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Tue, 7 Apr 2026 16:56:07 -0400 Subject: [PATCH 1/3] fix(cliproxy): preserve saved codex model selections - stop the Codex free-plan compatibility guard from rewriting codex.settings.json - keep saved dashboard selections intact and rely on runtime fallback when needed - update reconcile coverage to lock the non-mutating behavior in place --- src/cliproxy/codex-plan-compatibility.ts | 10 +++------- .../codex-plan-compatibility-reconcile.test.ts | 12 ++++++------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/cliproxy/codex-plan-compatibility.ts b/src/cliproxy/codex-plan-compatibility.ts index ab524926..de81aaac 100644 --- a/src/cliproxy/codex-plan-compatibility.ts +++ b/src/cliproxy/codex-plan-compatibility.ts @@ -3,7 +3,6 @@ import { getProviderCatalog } from './model-catalog'; import { fetchCodexQuota } from './quota-fetcher-codex'; import { getCachedQuota, setCachedQuota } from './quota-response-cache'; import type { CodexQuotaResult } from './quota-types'; -import { updateSettingsModel } from './services/variant-settings'; import { info, warn } from '../utils/ui'; export type CodexPlanType = CodexQuotaResult['planType']; @@ -146,7 +145,7 @@ export async function reconcileCodexModelForActivePlan( }, deps: CodexPlanCompatibilityDeps = {} ): Promise { - const { settingsPath, currentModel, verbose } = options; + const { currentModel, verbose } = options; if (!currentModel) return; const fallbackModel = getFreePlanFallbackCodexModel(currentModel); @@ -175,13 +174,10 @@ export async function reconcileCodexModelForActivePlan( } if (quota.planType === 'free') { - updateSettingsModel(settingsPath, fallbackModel, 'codex', { - rewriteHaikuModel: (haikuModel) => getFreePlanFallbackCodexModel(haikuModel) ?? haikuModel, - }); console.error( formatInfo( - `Codex free plan detected. Switched unsupported model "${normalizeCodexModelId(currentModel)}" ` + - `to "${fallbackModel}".` + `Codex free plan detected. Keeping saved model "${normalizeCodexModelId(currentModel)}" in settings; ` + + `runtime requests will fall back to "${fallbackModel}" when needed.` ) ); return; diff --git a/tests/unit/cliproxy/codex-plan-compatibility-reconcile.test.ts b/tests/unit/cliproxy/codex-plan-compatibility-reconcile.test.ts index b58148e2..934c7d13 100644 --- a/tests/unit/cliproxy/codex-plan-compatibility-reconcile.test.ts +++ b/tests/unit/cliproxy/codex-plan-compatibility-reconcile.test.ts @@ -45,7 +45,7 @@ async function importCompatibilityModule(cacheTag: string) { const identity = (message: string) => message; describe('codex plan compatibility reconcile', () => { - it('repairs stale paid-only Codex settings for free-plan accounts before launch', async () => { + it('keeps saved Codex settings intact and warns about runtime fallback for free-plan accounts', async () => { const { tmpDir, settingsPath } = createCodexSettingsFixture('gpt-5.3-codex-spark'); const errorSpy = spyOn(console, 'error').mockImplementation(() => {}); @@ -76,12 +76,12 @@ describe('codex plan compatibility reconcile', () => { const repaired = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')) as { env: Record; }; - expect(repaired.env.ANTHROPIC_MODEL).toBe('gpt-5-codex'); - expect(repaired.env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('gpt-5-codex'); - expect(repaired.env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('gpt-5-codex'); - expect(repaired.env.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('gpt-5-codex-mini'); + expect(repaired.env.ANTHROPIC_MODEL).toBe('gpt-5.3-codex'); + expect(repaired.env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('gpt-5.3-codex'); + expect(repaired.env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('gpt-5.3-codex'); + expect(repaired.env.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('gpt-5.3-codex-spark'); expect(errorSpy).toHaveBeenCalledWith( - 'Codex free plan detected. Switched unsupported model "gpt-5.3-codex" to "gpt-5-codex".' + 'Codex free plan detected. Keeping saved model "gpt-5.3-codex" in settings; runtime requests will fall back to "gpt-5-codex" when needed.' ); } finally { fs.rmSync(tmpDir, { recursive: true, force: true }); From 3f36c09064157f4afe306eef9b61d837fde91d66 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Tue, 7 Apr 2026 16:56:22 -0400 Subject: [PATCH 2/3] docs(roadmap): clarify codex runtime fallback behavior --- docs/project-roadmap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/project-roadmap.md b/docs/project-roadmap.md index a895fe83..6aebe050 100644 --- a/docs/project-roadmap.md +++ b/docs/project-roadmap.md @@ -59,7 +59,7 @@ All major modularization work is complete. The codebase evolved from monolithic - **2026-03-17**: Deprecated user-facing GLMT discovery across CLI help, completions, presets, and docs. Existing `glmt` profiles now run through a compatibility path that normalizes legacy proxy settings to the direct GLM endpoint. - **#748**: API profile creation now keeps provider selection compact by collapsing advanced presets behind an explicit toggle, shrinking chooser cards so the form fields stay visually primary, and giving `llama.cpp` a dedicated provider logo. - **#744**: API profile creation now keeps featured providers in a horizontal rail with scroll fallback, moves Anthropic Direct API to the end, reuses the shared Claude logo, and separates the custom-endpoint entry point from advanced template discovery. -- **#724**: Codex startup is now free-plan safe. CCS defaults new Codex sessions to a cross-plan model and auto-repairs stale paid-only Codex defaults when the active account is on the free plan. +- **#724**: Codex startup is now free-plan safe. CCS defaults new Codex sessions to a cross-plan model and uses runtime fallback handling for unsupported paid-only models without rewriting the saved dashboard settings. - **#737**: Dashboard model pickers in Cursor, Copilot, and CLIProxy now use a searchable combobox with autofocus and explicit no-results states for large model catalogs. - **#736**: `ccs config` now supports explicit dashboard bind hosts via `--host`, and surfaces remote-access warnings plus reachable URLs when the effective bind is non-loopback. From c1d429470872ee726d1578797e00aac0f217554d Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Tue, 7 Apr 2026 17:59:56 -0400 Subject: [PATCH 3/3] fix(cliproxy): remove dead codex reconcile settings arg --- src/cliproxy/codex-plan-compatibility.ts | 1 - src/cliproxy/executor/index.ts | 1 - .../unit/cliproxy/codex-plan-compatibility-reconcile.test.ts | 5 ----- 3 files changed, 7 deletions(-) diff --git a/src/cliproxy/codex-plan-compatibility.ts b/src/cliproxy/codex-plan-compatibility.ts index de81aaac..95506089 100644 --- a/src/cliproxy/codex-plan-compatibility.ts +++ b/src/cliproxy/codex-plan-compatibility.ts @@ -139,7 +139,6 @@ export function resolveRuntimeCodexFallbackModel(options: { export async function reconcileCodexModelForActivePlan( options: { - settingsPath: string; currentModel: string | undefined; verbose: boolean; }, diff --git a/src/cliproxy/executor/index.ts b/src/cliproxy/executor/index.ts index 22771bb4..625216ec 100644 --- a/src/cliproxy/executor/index.ts +++ b/src/cliproxy/executor/index.ts @@ -883,7 +883,6 @@ export async function execClaudeWithCLIProxy( if (provider === 'codex' && !cfg.isComposite && !skipLocalAuth) { await reconcileCodexModelForActivePlan({ - settingsPath: cfg.customSettingsPath || getProviderSettingsPath(provider), currentModel: getCurrentModel(provider, cfg.customSettingsPath), verbose, }); diff --git a/tests/unit/cliproxy/codex-plan-compatibility-reconcile.test.ts b/tests/unit/cliproxy/codex-plan-compatibility-reconcile.test.ts index 934c7d13..4cdca89f 100644 --- a/tests/unit/cliproxy/codex-plan-compatibility-reconcile.test.ts +++ b/tests/unit/cliproxy/codex-plan-compatibility-reconcile.test.ts @@ -54,7 +54,6 @@ describe('codex plan compatibility reconcile', () => { await reconcileCodexModelForActivePlan( { - settingsPath, currentModel: 'gpt-5.3-codex', verbose: false, }, @@ -98,7 +97,6 @@ describe('codex plan compatibility reconcile', () => { await reconcileCodexModelForActivePlan( { - settingsPath, currentModel: 'gpt-5.3-codex', verbose: false, }, @@ -134,7 +132,6 @@ describe('codex plan compatibility reconcile', () => { await reconcileCodexModelForActivePlan( { - settingsPath, currentModel: 'gpt-5.3-codex', verbose: false, }, @@ -174,7 +171,6 @@ describe('codex plan compatibility reconcile', () => { await reconcileCodexModelForActivePlan( { - settingsPath, currentModel: 'gpt-5.3-codex', verbose: false, }, @@ -216,7 +212,6 @@ describe('codex plan compatibility reconcile', () => { await reconcileCodexModelForActivePlan( { - settingsPath, currentModel: 'gpt-5.3-codex', verbose: false, },