fix(native-chat): don't force a default model/effort on agent spawn (#9134)

#9085 made resolveNativeChatSessionOptionDefaults fall back to the catalog
default model (sonnet) and effort (high) whenever the user had not explicitly
picked one, injecting `--model sonnet --effort high` (and codex `-m`/`-c`
equivalents) onto every agent spawn: composer, worktree activation,
source-control agents, onboarding, work-item launch, floating terminal, and
new-tab launch. That silently overrode each user's own CLI-configured default
model.

Return undefined (spawn the agent exactly as its own CLI would) unless the user
has explicitly selected a model, and carry only explicitly stored option
values. An explicit model selection still applies that model and its catalog
option defaults via resolveAgentSessionOptionLaunch.
This commit is contained in:
Brennan Benson 2026-07-16 21:29:17 -07:00 committed by GitHub
parent 1b331f282c
commit 1284a00e93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 72 additions and 58 deletions

View File

@ -14,8 +14,7 @@ describe('buildOnboardingFolderAgentStartup', () => {
})
expect(startup).toEqual({
command:
"codex '-m' 'gpt-5.6-sol' '-c' 'model_reasoning_effort=medium' '--dangerously-bypass-approvals-and-sandbox'",
command: "codex '--dangerously-bypass-approvals-and-sandbox'",
env: {},
launchAgent: 'codex',
launchConfig: {
@ -23,7 +22,7 @@ describe('buildOnboardingFolderAgentStartup', () => {
agentArgs: '--dangerously-bypass-approvals-and-sandbox',
agentEnv: {}
},
sessionOptions: { model: 'gpt-5.6-sol', effort: 'medium' },
sessionOptions: undefined,
telemetry: {
agent_kind: 'codex',
launch_source: 'onboarding',
@ -99,8 +98,7 @@ describe('buildOnboardingFolderAgentStartup', () => {
false
)
).toEqual({
command:
"echo onboarding-folder-agent '-m' 'gpt-5.6-sol' '-c' 'model_reasoning_effort=medium' '--dangerously-bypass-approvals-and-sandbox'",
command: "echo onboarding-folder-agent '--dangerously-bypass-approvals-and-sandbox'",
env: {},
launchAgent: 'codex',
launchConfig: {
@ -108,7 +106,7 @@ describe('buildOnboardingFolderAgentStartup', () => {
agentArgs: '--dangerously-bypass-approvals-and-sandbox',
agentEnv: {}
},
sessionOptions: { model: 'gpt-5.6-sol', effort: 'medium' },
sessionOptions: undefined,
telemetry: {
agent_kind: 'codex',
launch_source: 'onboarding',

View File

@ -340,8 +340,7 @@ describe('launchAgentInNewTab', () => {
environmentId: 'web-runtime',
targetGroupId: 'group-1',
activate: true,
command:
"codex '-m' 'gpt-5.6-sol' '-c' 'model_reasoning_effort=medium' '--model' 'gpt-5' '--reasoning-effort' 'high' 'fix the spinner'",
command: "codex '--model' 'gpt-5' '--reasoning-effort' 'high' 'fix the spinner'",
env: { CODEX_PROFILE: 'captured' },
startupCommandDelivery: 'shell-ready',
launchConfig: {
@ -486,8 +485,7 @@ describe('launchAgentInNewTab', () => {
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect.objectContaining({
command:
"claude '--model' 'sonnet' '--effort' 'high' '--dangerously-skip-permissions' --prefill 'review Bob''s change'"
command: "claude '--dangerously-skip-permissions' --prefill 'review Bob''s change'"
})
)
})
@ -505,7 +503,7 @@ describe('launchAgentInNewTab', () => {
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect.objectContaining({
command: 'claude "--model" "sonnet" "--effort" "high" "--dangerously-skip-permissions"'
command: 'claude "--dangerously-skip-permissions"'
})
)
})
@ -523,7 +521,7 @@ describe('launchAgentInNewTab', () => {
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect.objectContaining({
command: "claude '--model' 'sonnet' '--effort' 'high' '--dangerously-skip-permissions'"
command: "claude '--dangerously-skip-permissions'"
})
)
})
@ -543,8 +541,7 @@ describe('launchAgentInNewTab', () => {
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect.objectContaining({
command:
'codex "-m" "gpt-5.6-sol" "-c" "model_reasoning_effort=medium" "--model" "gpt-5" "fix the spinner"'
command: 'codex "--model" "gpt-5" "fix the spinner"'
})
)
})
@ -564,8 +561,7 @@ describe('launchAgentInNewTab', () => {
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect.objectContaining({
command:
"claude '--model' 'sonnet' '--effort' 'high' '--dangerously-skip-permissions' --prefill 'review Bob'\\''s change'"
command: "claude '--dangerously-skip-permissions' --prefill 'review Bob'\\''s change'"
})
)
})
@ -583,7 +579,7 @@ describe('launchAgentInNewTab', () => {
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect.objectContaining({
command: "claude '--model' 'sonnet' '--effort' 'high' '--dangerously-skip-permissions'"
command: "claude '--dangerously-skip-permissions'"
})
)
})
@ -620,8 +616,7 @@ describe('launchAgentInNewTab', () => {
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect.objectContaining({
command:
"claude '--model' 'sonnet' '--effort' 'high' '--dangerously-skip-permissions' --prefill 'review Bob'\\''s change'"
command: "claude '--dangerously-skip-permissions' --prefill 'review Bob'\\''s change'"
})
)
})
@ -642,7 +637,7 @@ describe('launchAgentInNewTab', () => {
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect.objectContaining({
command: "claude '--model' 'sonnet' '--effort' 'high' '--dangerously-skip-permissions'"
command: "claude '--dangerously-skip-permissions'"
})
)
expect(mockPasteDraftWhenAgentReady).toHaveBeenCalledWith(
@ -848,7 +843,7 @@ describe('launchAgentInNewTab', () => {
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect.objectContaining({
command: "codex '-m' 'gpt-5.6-sol' '-c' 'model_reasoning_effort=medium' '--model' 'gpt-5.5'"
command: "codex '--model' 'gpt-5.5'"
})
)
})

View File

@ -402,7 +402,7 @@ describe('launchWorkItemDirect', () => {
cmdOverrides: {},
agentArgs: '--dangerously-skip-permissions',
agentEnv: {},
sessionOptions: { model: 'sonnet', effort: 'high' },
sessionOptions: undefined,
platform: 'win32',
isRemote: false
})
@ -526,7 +526,7 @@ describe('launchWorkItemDirect', () => {
cmdOverrides: {},
agentArgs: '--yolo',
agentEnv: {},
sessionOptions: { model: 'auto' },
sessionOptions: undefined,
platform: 'linux',
isRemote: true
})
@ -536,7 +536,7 @@ describe('launchWorkItemDirect', () => {
cmdOverrides: {},
agentArgs: '--yolo',
agentEnv: {},
sessionOptions: { model: 'auto' },
sessionOptions: undefined,
platform: 'linux',
isRemote: true,
allowEmptyPromptLaunch: true

View File

@ -96,8 +96,7 @@ describe('activateAndRevealWorktree created agent reopen', () => {
expect(result).toEqual({ primaryTabId: reopenedTab?.id })
expect(reopenedTab).toBeDefined()
expect(state.pendingStartupByTabId[reopenedTab!.id]).toEqual({
command:
"codex '-m' 'gpt-5.6-sol' '-c' 'model_reasoning_effort=medium' '--dangerously-bypass-approvals-and-sandbox'",
command: "codex '--dangerously-bypass-approvals-and-sandbox'",
env: {},
launchAgent: 'codex',
launchConfig: {
@ -106,7 +105,7 @@ describe('activateAndRevealWorktree created agent reopen', () => {
agentEnv: {}
},
launchToken: expect.any(String),
sessionOptions: { model: 'gpt-5.6-sol', effort: 'medium' },
sessionOptions: undefined,
telemetry: {
agent_kind: 'codex',
launch_source: 'sidebar',

View File

@ -58,8 +58,7 @@ describe('repo slice skipped-onboarding folder startup', () => {
{
sidebarRevealBehavior: 'auto',
startup: {
command:
"codex '-m' 'gpt-5.6-sol' '-c' 'model_reasoning_effort=medium' '--dangerously-bypass-approvals-and-sandbox'",
command: "codex '--dangerously-bypass-approvals-and-sandbox'",
env: {},
launchAgent: 'codex',
launchConfig: {
@ -67,7 +66,7 @@ describe('repo slice skipped-onboarding folder startup', () => {
agentArgs: '--dangerously-bypass-approvals-and-sandbox',
agentEnv: {}
},
sessionOptions: { model: 'gpt-5.6-sol', effort: 'medium' },
sessionOptions: undefined,
telemetry: {
agent_kind: 'codex',
launch_source: 'onboarding',

View File

@ -72,7 +72,7 @@ describe('agent session option catalog', () => {
})
})
it('restores defaults per model without leaking values across models', () => {
it('resolves only stored values without leaking values across models', () => {
let persisted = updateNativeChatSessionOptionDefaults({
persisted: undefined,
agent: 'claude',
@ -96,17 +96,56 @@ describe('agent session option catalog', () => {
})
expect(resolveNativeChatSessionOptionDefaults(persisted, 'claude')).toEqual({
model: 'sonnet',
effort: 'high'
model: 'sonnet'
})
expect(persisted.claude?.valuesByModel?.opus).toEqual({ effort: 'xhigh' })
})
it('seeds a fresh launch from the catalog defaults', () => {
expect(resolveNativeChatSessionOptionDefaults(undefined, 'claude')).toEqual({
model: 'sonnet',
effort: 'high'
})
it('spawns vanilla when the user has not explicitly selected a model', () => {
// Regression (#9085): a fresh launch must not force the catalog default
// model/effort — the agent must spawn exactly as its own CLI would.
expect(resolveNativeChatSessionOptionDefaults(undefined, 'claude')).toBeUndefined()
expect(resolveNativeChatSessionOptionDefaults({}, 'claude')).toBeUndefined()
expect(resolveNativeChatSessionOptionDefaults({}, 'future-agent')).toBeUndefined()
})
it('resolves an explicitly selected model and only its stored options', () => {
let persisted = updateNativeChatSessionOptionDefaults({
persisted: undefined,
agent: 'claude',
modelId: 'opus',
optionId: 'model',
value: 'opus'
})
expect(resolveNativeChatSessionOptionDefaults(persisted, 'claude')).toEqual({
model: 'opus'
})
persisted = updateNativeChatSessionOptionDefaults({
persisted,
agent: 'claude',
modelId: 'opus',
optionId: 'effort',
value: 'xhigh'
})
expect(resolveNativeChatSessionOptionDefaults(persisted, 'claude')).toEqual({
model: 'opus',
effort: 'xhigh'
})
})
it('keeps catalog option defaults after the user explicitly selects a model', () => {
const persisted = updateNativeChatSessionOptionDefaults({
persisted: undefined,
agent: 'claude',
modelId: 'sonnet',
optionId: 'model',
value: 'sonnet'
})
const defaults = resolveNativeChatSessionOptionDefaults(persisted, 'claude')
expect(resolveAgentSessionOptionLaunch('claude', defaults)).toEqual({
args: ['--model', 'sonnet', '--effort', 'high'],
appliedValues: { model: 'sonnet', effort: 'high' }
})
})
})

View File

@ -1,10 +1,5 @@
import type { AgentType } from './agent-status-types'
import {
catalogDefaultModel,
findCatalogModel,
getAgentSessionOptionCatalog,
sessionOptionValueIsValid
} from './agent-session-option-catalog'
import { sessionOptionValueIsValid } from './agent-session-option-catalog'
import type {
PersistedNativeChatSessionOptions,
SessionOptionValue
@ -14,16 +9,10 @@ export function resolveNativeChatSessionOptionDefaults(
persisted: PersistedNativeChatSessionOptions | null | undefined,
agent: AgentType
): Record<string, SessionOptionValue> | undefined {
const catalog = getAgentSessionOptionCatalog(agent)
const entry = persisted?.[agent]
// Why: fresh launches must be authoritative too, so resolve the catalog's
// declared fallback before the launch command is assembled.
const modelId =
typeof entry?.model === 'string' && entry.model.trim()
? entry.model
: catalog
? catalogDefaultModel(catalog)?.id
: undefined
// Why: untouched settings must preserve the agent CLI's configured defaults;
// only a model explicitly selected by the user authorizes launch flags.
const modelId = typeof entry?.model === 'string' && entry.model.trim() ? entry.model : undefined
if (!modelId) {
return undefined
}
@ -36,11 +25,6 @@ export function resolveNativeChatSessionOptionDefaults(
}
}
}
const model = catalog ? findCatalogModel(catalog, modelId) : undefined
for (const option of model?.options ?? []) {
values[option.id] ??= option.kind.defaultValue
}
return values
}