From da450bd698c36d7fe9ef9712d1f7190db47c17dd Mon Sep 17 00:00:00 2001 From: Richard Solomou Date: Tue, 31 Mar 2026 14:03:10 +0300 Subject: [PATCH 1/2] fix(code): persist model selection during task creation The model selected in the task creation UI was lost because it was only stored in the preview session's Zustand state, which could be transiently reset. Additionally, the agent started with the default model (opus) and was changed post-creation via IPC, introducing a race window. Fix by tracking the selection in local React state and passing it through agent.start so the agent initializes with the correct model. Fixes #1380 --- apps/code/src/main/services/agent/schemas.ts | 3 +++ apps/code/src/main/services/agent/service.ts | 5 +++++ .../renderer/features/sessions/service/service.ts | 1 + .../features/task-detail/components/TaskInput.tsx | 15 +++++++++++++-- .../agent/src/adapters/claude/claude-agent.ts | 8 +++++--- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/apps/code/src/main/services/agent/schemas.ts b/apps/code/src/main/services/agent/schemas.ts index dafcaf169..5aca01366 100644 --- a/apps/code/src/main/services/agent/schemas.ts +++ b/apps/code/src/main/services/agent/schemas.ts @@ -30,6 +30,8 @@ export const sessionConfigSchema = z.object({ additionalDirectories: z.array(z.string()).optional(), /** Permission mode to use for the session (e.g. "default", "acceptEdits", "plan", "bypassPermissions") */ permissionMode: z.string().optional(), + /** Preferred model ID for the session (e.g. "claude-sonnet-4-6") */ + model: z.string().optional(), }); export type SessionConfig = z.infer; @@ -49,6 +51,7 @@ export const startSessionInput = z.object({ additionalDirectories: z.array(z.string()).optional(), customInstructions: z.string().max(2000).optional(), effort: effortLevelSchema.optional(), + model: z.string().optional(), }); export type StartSessionInput = z.infer; diff --git a/apps/code/src/main/services/agent/service.ts b/apps/code/src/main/services/agent/service.ts index 9d64c2ac9..f36f34205 100644 --- a/apps/code/src/main/services/agent/service.ts +++ b/apps/code/src/main/services/agent/service.ts @@ -189,6 +189,8 @@ interface SessionConfig { customInstructions?: string; /** Effort level for Claude sessions */ effort?: EffortLevel; + /** Preferred model ID for the session (e.g. "claude-sonnet-4-6") */ + model?: string; } interface ManagedSession { @@ -465,6 +467,7 @@ export class AgentService extends TypedEventEmitter { permissionMode, customInstructions, effort, + model, } = config; // Preview sessions don't need a real repo — use a temp directory @@ -673,6 +676,7 @@ export class AgentService extends TypedEventEmitter { options: { ...(additionalDirectories?.length && { additionalDirectories }), ...(effort && { effort }), + ...(model && { model }), plugins, }, }, @@ -1362,6 +1366,7 @@ For git operations while detached: customInstructions: "customInstructions" in params ? params.customInstructions : undefined, effort: "effort" in params ? params.effort : undefined, + model: "model" in params ? params.model : undefined, }; } diff --git a/apps/code/src/renderer/features/sessions/service/service.ts b/apps/code/src/renderer/features/sessions/service/service.ts index 5eb919f47..887248989 100644 --- a/apps/code/src/renderer/features/sessions/service/service.ts +++ b/apps/code/src/renderer/features/sessions/service/service.ts @@ -548,6 +548,7 @@ export class SessionService { effort: effortLevelSchema.safeParse(reasoningLevel).success ? (reasoningLevel as EffortLevel) : undefined, + model, }); const session = this.createBaseSession(taskRun.id, taskId, taskTitle); diff --git a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx index ad0edc9d2..09c1b6424 100644 --- a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx +++ b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx @@ -78,6 +78,11 @@ export function TaskInput({ onTaskCreated }: TaskInputProps = {}) { null, ); + // Track the user's explicit model selection independently of the preview + // session. The preview session's config can be reset (e.g. adapter change), + // which would lose the user's choice if we only read from the store. + const [selectedModel, setSelectedModel] = useState(null); + const [selectedDirectory, setSelectedDirectory] = useState(""); const workspaceMode = lastUsedWorkspaceMode || "local"; const adapter = lastUsedAdapter; @@ -94,8 +99,10 @@ export function TaskInput({ onTaskCreated }: TaskInputProps = {}) { setLastUsedLocalWorkspaceMode(mode); } }; - const setAdapter = (newAdapter: AgentAdapter) => + const setAdapter = (newAdapter: AgentAdapter) => { setLastUsedAdapter(newAdapter); + setSelectedModel(null); + }; const { githubIntegration, repositories, isLoadingRepos } = useRepositoryIntegration(); @@ -192,8 +199,11 @@ export function TaskInput({ onTaskCreated }: TaskInputProps = {}) { // Get current values from preview session config options for task creation. // Defaults ensure values are always passed even before the preview session loads. - const currentModel = + // Prefer the user's explicit selection (local state) over the preview session value, + // since the preview session's config can be transiently reset. + const previewModel = modelOption?.type === "select" ? modelOption.currentValue : undefined; + const currentModel = selectedModel ?? previewModel; const modeFallback = defaultInitialTaskMode === "last_used" ? lastUsedInitialTaskMode : "plan"; const currentExecutionMode = @@ -461,6 +471,7 @@ export function TaskInput({ onTaskCreated }: TaskInputProps = {}) { previewTaskId={previewTaskId} onAdapterChange={setAdapter} isPreviewConnecting={isConnecting} + onModelChange={setSelectedModel} /> Date: Wed, 1 Apr 2026 09:06:40 -0700 Subject: [PATCH 2/2] resolve merge conflicts in model selection plumbing --- apps/code/src/main/services/agent/service.ts | 3 ++- packages/agent/src/adapters/claude/claude-agent.ts | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/code/src/main/services/agent/service.ts b/apps/code/src/main/services/agent/service.ts index f36f34205..27f2881d6 100644 --- a/apps/code/src/main/services/agent/service.ts +++ b/apps/code/src/main/services/agent/service.ts @@ -189,7 +189,7 @@ interface SessionConfig { customInstructions?: string; /** Effort level for Claude sessions */ effort?: EffortLevel; - /** Preferred model ID for the session (e.g. "claude-sonnet-4-6") */ + /** Model to use for the session (e.g. "claude-sonnet-4-6") */ model?: string; } @@ -647,6 +647,7 @@ export class AgentService extends TypedEventEmitter { additionalDirectories, }), ...(effort && { effort }), + ...(model && { model }), plugins, }, }, diff --git a/packages/agent/src/adapters/claude/claude-agent.ts b/packages/agent/src/adapters/claude/claude-agent.ts index dff266a02..7bef5cbbc 100644 --- a/packages/agent/src/adapters/claude/claude-agent.ts +++ b/packages/agent/src/adapters/claude/claude-agent.ts @@ -780,7 +780,9 @@ export class ClaudeAcpAgent extends BaseAcpAgent { const meta = params._meta as NewSessionMeta | undefined; const taskId = meta?.persistence?.taskId; const effort = meta?.claudeCode?.options?.effort as EffortLevel | undefined; - const metaModel = meta?.claudeCode?.options?.model as string | undefined; + const modelOverride = meta?.claudeCode?.options?.model as + | string + | undefined; // We want to create a new session id unless it is resume, // but not resume + forkSession. @@ -922,9 +924,11 @@ export class ClaudeAcpAgent extends BaseAcpAgent { // Resolve model: explicit UI selection takes priority, then settings, then gateway default const settingsModel = settingsManager.getSettings().model; - const modelOptions = await this.getModelConfigOptions(metaModel); + const modelOptions = await this.getModelConfigOptions( + modelOverride ?? undefined, + ); const resolvedModelId = - metaModel || settingsModel || modelOptions.currentModelId; + modelOverride || settingsModel || modelOptions.currentModelId; session.modelId = resolvedModelId; session.lastContextWindowSize = this.getContextWindowForModel(resolvedModelId);