From 2dc0df462b8a90d97bc644276bd9ba9617e9d0f2 Mon Sep 17 00:00:00 2001 From: Calvin <35207870+zzz163519@users.noreply.github.com> Date: Mon, 9 Nov 2026 19:36:51 +0800 Subject: [PATCH] fix(claude): verify model switches when terminal echoes omit vendor prefix (#40064) Claude echoes "Set model to Opus 5.5" without the "Claude" vendor prefix, and never echoes the picker-only " alias" suffix or a raw "claude-" id prefix. The confirmation observer required every picker-label token, so a successful switch resolved to unknown and showed "Could not verify the model change". Normalize the expected label before matching; context tokens such as 1M are still required. Fixes #40058 Co-authored-by: Neil --- .../claude-model-switch-confirmation.test.ts | 32 +++++++++++++++++++ .../claude-model-switch-confirmation.ts | 6 ++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/components/native-chat/session/options/terminal/claude-model-switch-confirmation.test.ts b/src/renderer/src/components/native-chat/session/options/terminal/claude-model-switch-confirmation.test.ts index aa543c8b397..f2879c0d4e6 100644 --- a/src/renderer/src/components/native-chat/session/options/terminal/claude-model-switch-confirmation.test.ts +++ b/src/renderer/src/components/native-chat/session/options/terminal/claude-model-switch-confirmation.test.ts @@ -4,6 +4,38 @@ import { describe, expect, it, vi } from 'vitest' import { createClaudeModelSwitchConfirmationObserver } from './claude-model-switch-confirmation' describe('Claude model switch confirmation detection', () => { + it.each([ + [ + 'quoted model name', + 'Claude Opus 5.5', + 'Set model to `Opus 5.5` and saved as your default for new sessions' + ], + [ + 'matching requested context variant', + 'Claude Opus 5.5 (1M context)', + 'Set model to `Opus 5.5 (1M context)` and saved as your default for new sessions' + ], + ['raw model id fallback', 'claude-opus-5-5', 'Set model to Opus 5.5'], + ['alias picker row', 'Opus alias', 'Set model to Opus 5.5'] + ])('confirms a picker label with %s', async (_name, label, output) => { + const dataObserver = { current: (_data: string): void => {} } + const observer = createClaudeModelSwitchConfirmationObserver({ + ptyId: 'pty-1', + expectedModelLabel: label, + subscribeToData: (watcher) => { + dataObserver.current = watcher + return () => {} + } + }) + + await observer.ready + observer.arm() + observer.startDetection() + dataObserver.current(output) + + await expect(observer.result).resolves.toBe('applied') + }) + it('reports matching success output split across PTY chunks', async () => { const dataObserver = { current: (_data: string): void => {} } const unsubscribe = vi.fn(() => {}) diff --git a/src/renderer/src/components/native-chat/session/options/terminal/claude-model-switch-confirmation.ts b/src/renderer/src/components/native-chat/session/options/terminal/claude-model-switch-confirmation.ts index 3c614586f10..803430cad19 100644 --- a/src/renderer/src/components/native-chat/session/options/terminal/claude-model-switch-confirmation.ts +++ b/src/renderer/src/components/native-chat/session/options/terminal/claude-model-switch-confirmation.ts @@ -33,12 +33,14 @@ function compactTerminalText(buffer: string): string { function hasClaudeModelSwitchSuccess(buffer: string, modelLabel: string): boolean { const text = compactTerminalText(buffer) - const marker = `setmodelto${modelLabel.replace(/\s+/g, '').toLowerCase()}` + // Claude echoes neither the vendor prefix nor Orca's picker-only `alias` suffix. + const label = modelLabel.replace(/^claude[\s-]+/i, '').replace(/\s+alias$/i, '') + const marker = `setmodelto${label.replace(/\s+/g, '').toLowerCase()}` if (text.includes(marker)) { return true } // Match every picker token so a versioned echo cannot confirm the wrong context variant. - const labelTokens = modelLabel.toLowerCase().match(/[a-z]+|\d+[a-z]*/g) ?? [] + const labelTokens = label.toLowerCase().match(/[a-z]+|\d+[a-z]*/g) ?? [] const successStart = text.lastIndexOf('setmodelto') if (successStart === -1 || labelTokens.length === 0) { return false