diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 2399ea72c..93370f24d 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -885,6 +885,10 @@ export async function buildSessionOptions( // - supports --provider --model // - supports --model / const modelMatchPreferences = getModelMatchPreferences(activeSettings); + // True when a configured `default` role was deliberately left unresolved for + // createAgentSession's post-extension re-resolution (issue #6694); the + // scoped thinking-level seed below must be deferred along with the model. + let deferredDefaultRole = false; if (parsed.model) { const resolved = resolveCliModel({ cliProvider: parsed.provider, @@ -953,9 +957,13 @@ export async function buildSessionOptions( // here and the session would run on an unrelated in-scope provider without // any error. Leaving `options.model` unset lets createAgentSession's // post-extension default-role resolution reclaim it against the fully - // registered, still enabledModels-scoped catalog (issue #6694). Only seed - // the first scoped model when no default role is configured to reclaim. - if (!options.model && !remembered) options.model = scopedModels[0].model; + // registered, still enabledModels-scoped catalog (issue #6694). + // Defer ONLY for a settings-derived scope: createAgentSession re-resolves + // against `settings.enabledModels` and never sees CLI `--models`, so + // deferring under an explicit CLI scope would let the saved default + // escape it — keep pinning the first scoped model there. + deferredDefaultRole = !options.model && Boolean(remembered) && !((parsed.models?.length ?? 0) > 0); + if (!options.model && !deferredDefaultRole) options.model = scopedModels[0].model; } if (parsed.noPrewalk && (parsed.prewalk || parsed.prewalkInto !== undefined)) { @@ -1014,6 +1022,10 @@ export async function buildSessionOptions( } else if ( scopedModels.length > 0 && scopedModels[0].explicitThinkingLevel === true && + // A deferred default role resolves its own model (and any explicit + // thinking suffix) after extensions register; seeding the fallback + // scoped model's level here would override it in createAgentSession. + !deferredDefaultRole && !parsed.continue && !parsed.resume ) { diff --git a/packages/coding-agent/test/sdk-model-selection.test.ts b/packages/coding-agent/test/sdk-model-selection.test.ts index 59cbb490a..213b33c83 100644 --- a/packages/coding-agent/test/sdk-model-selection.test.ts +++ b/packages/coding-agent/test/sdk-model-selection.test.ts @@ -1093,4 +1093,44 @@ describe("createAgentSession deferred model pattern resolution", () => { exitSpy.mockRestore(); } }); + test("pins the first CLI --models scoped model even when the saved default role is out of scope", async () => { + const scopedTarget = getBundledModel("openai", "gpt-4o-mini"); + const savedDefault = getBundledModel("anthropic", "claude-sonnet-4-5"); + if (!scopedTarget || !savedDefault) { + throw new Error("Expected bundled openai and anthropic models"); + } + const authStorage = await AuthStorage.create(path.join(tempDir, "cli-scope-auth.db")); + authStoragesToClose.push(authStorage); + authStorage.setRuntimeApiKey(scopedTarget.provider, "test-key"); + authStorage.setRuntimeApiKey(savedDefault.provider, "test-key"); + const modelRegistry = new ModelRegistry(authStorage, path.join(tempDir, "cli-scope-models.yml")); + const settings = Settings.isolated({}); + settings.setModelRole("default", `${savedDefault.provider}/${savedDefault.id}`); + + const parsed = parseArgs(["--models", `${scopedTarget.provider}/${scopedTarget.id}`]); + const scopedModels = await resolveModelScope( + parsed.models ?? [], + modelRegistry, + getModelMatchPreferences(settings), + settings, + ); + expect(scopedModels.map(scoped => `${scoped.model.provider}/${scoped.model.id}`)).toEqual([ + `${scopedTarget.provider}/${scopedTarget.id}`, + ]); + + const cliOptions = await buildCliSessionOptions( + parsed, + scopedModels, + SessionManager.inMemory(), + modelRegistry, + settings, + ); + // The issue #6694 deferral must NOT apply to an explicit CLI scope: + // createAgentSession re-resolves the default role against + // `settings.enabledModels` only and never sees `--models`, so leaving + // `options.model` unset would let the saved out-of-scope default + // silently escape the scope the user just asked for. + expect(cliOptions.model?.provider).toBe(scopedTarget.provider); + expect(cliOptions.model?.id).toBe(scopedTarget.id); + }); });