Skip to content

fix: prevent DeepSeek 400 error on direct mode switch from reasoning-capable model#994

Open
carmonium wants to merge 1 commit into
Zoo-Code-Org:mainfrom
carmonium:fix/mode-switch-reasoning-guard
Open

fix: prevent DeepSeek 400 error on direct mode switch from reasoning-capable model#994
carmonium wants to merge 1 commit into
Zoo-Code-Org:mainfrom
carmonium:fix/mode-switch-reasoning-guard

Conversation

@carmonium

@carmonium carmonium commented Jul 23, 2026

Copy link
Copy Markdown

Problem

When switching directly (without delegating via new_task) from a mode using a
reasoning-capable model (e.g., Claude Sonnet in orchestrator mode) to a mode
using a provider with strict structural expectations (e.g., DeepSeek in code
mode), the API request fails with a deterministic 400 error:

Provider Error · 400
DeepSeek completion error: 400 The `reasoning_content` in the thinking mode
must be passed back to the API.

The root cause is a three-step chain:

  1. Mode-switch bypass preserves history. SwitchModeTool.execute()
    calls ClineProvider.handleModeSwitch(), which mutates _taskMode on the
    same Task instance — no new task is created, no parentTaskId is set,
    and the entire conversation history built under the previous model is carried
    over unchanged.

  2. DeepSeek (and similar providers) require reasoning_content. When
    thinking mode is enabled, DeepSeek's API requires that every assistant
    message with tool_calls in the conversation history also include a
    reasoning_content field. The same requirement applies to Z.ai/GLM and MiMo
    providers (documented in their respective provider files).

  3. Inherited history never had reasoning_content. Messages generated by
    providers that don't use DeepSeek-style reasoning (e.g., Claude) never
    produce this field. Even if they did, buildCleanConversationHistory() in
    Task.ts strips it in the default message path.

The result is an unrecoverable error loop — the 400 is deterministic (not a
transient network failure), so automatic retries also fail.

Solution — Two-Layer Defense-in-Depth

Layer 1: Provider Guard (Unconditional Safety Net)

A new shared utility function historyHasToolCallsWithoutReasoning() detects
whether converted message history contains assistant messages with tool_calls
but no non-empty reasoning_content. It is integrated into three providers
with strict reasoning_content requirements:

  • deepseek.ts: After convertToR1Format(), computes
    effectiveThinkingEnabled = isThinkingModel && !hasIncompatibleHistory and
    uses it for all downstream thinking/effort/temperature decisions. Emits a
    provider_reasoning_guard_triggered structured log when triggered.

  • zai.ts: After convertToZAiFormat(), forces useReasoning = false
    when incompatible history is detected, disabling thinking and clearing
    reasoning_effort.

  • mimo.ts: After convertToR1Format(), switches extra_body.thinking
    from { type: "enabled" } to { type: "disabled" } when incompatible
    history is detected — the first disable path MiMo has ever had.

Layer 2: Orchestration Guard (UI Warning + Optional Auto-Condense)

A new utility modeSwitchRisksReasoningIncompatibility() checks whether a
mode switch from one provider to another risks carrying over incompatible
history (target is a strict provider, source is not).

Integrated into ClineProvider.handleModeSwitch():

  • Behavior A (always-on, default): When via !== "new_task_delegation"
    and the check is positive, emits a visible, non-interactive chat warning
    (mode_switch_compatibility_warning) informing the user that the
    conversation history may be incompatible with the new provider.

  • Behavior B (opt-in): When autoCondenseOnRiskyModeSwitch workspace
    setting is true, additionally auto-condenses the conversation via
    task.condenseContext() before the mode mutation, replacing structured
    tool_calls history with a plain-text summary.

Files Changed

New files:

  • src/api/providers/utils/reasoning-history-guard.ts — Layer 1 guard function
  • src/api/providers/utils/__tests__/reasoning-history-guard.spec.ts — 9 tests
  • src/shared/reasoning-mode-compatibility.ts — Layer 2 utility functions
  • src/shared/__tests__/reasoning-mode-compatibility.spec.ts — 14 tests

Modified files:

  • src/api/providers/deepseek.ts — Layer 1 guard integration
  • src/api/providers/zai.ts — Layer 1 guard integration
  • src/api/providers/mimo.ts — Layer 1 guard integration (first disable path)
  • src/core/webview/ClineProvider.ts — Layer 2 guard in handleModeSwitch()
  • src/core/tools/SwitchModeTool.ts — pass via: "switch_mode" to handler
  • packages/types/src/message.ts — new clineSays value
  • webview-ui/src/components/chat/ChatRow.tsx — render new warning via WarningRow
  • webview-ui/src/i18n/locales/en/chat.json — i18n strings for warning

Testing

Unit Tests

  • reasoning-history-guard.spec.ts: 9 tests covering empty messages, tool_calls
    with/without reasoning_content, empty reasoning_content, empty tool_calls,
    non-assistant tool_calls, multi-message detection, non-array tool_calls.
  • reasoning-mode-compatibility.spec.ts: 14 tests covering strict provider
    identification, same-provider switches, non-strict→strict, strict→non-strict,
    strict↔strict, undefined (unknown) provider fail-safe behavior.

Manual Verification

  • Zero regressions on the happy path (normal, non-bypassed mode switches and
    normal thinking-mode requests are unaffected).
  • The exact incident scenario (Orchestrator → bypass switch_mode to
    Code/DeepSeek) was traced through the new code path; the original 400 error
    is now structurally prevented by Layer 1 independent of Layer 2.
  • Typecheck: clean, zero new errors.

Caveats

  • Only tested against DeepSeek, Z.ai, and MiMo providers (the three with
    documented reasoning_content strictness). Other providers with
    preserveReasoning: true (Moonshot, MiniMax, Fireworks, Bedrock,
    opencode-go) share the same structural risk but are not yet covered — they
    are tracked as a follow-up.
  • Layer 1 disables thinking mode for the affected request rather than
    synthesizing fake reasoning_content, which is a deliberate trade-off:
    deterministic safety over risk of undefined provider-side behavior.
  • Layer 2's auto-condense behavior (Behavior B) defaults to false because
    condenseContext() has real latency/token cost and a fidelity trade-off
    that should be opt-in.

Summary by CodeRabbit

  • New Features

    • Added warnings when switching modes may create provider compatibility issues.
    • Automatically disables reasoning when conversation history is incompatible, helping prevent request failures.
    • Added chat messaging explaining risky mode switches and affected providers.
  • Bug Fixes

    • Prevented API errors caused by tool-call history missing required reasoning details.
  • Tests

    • Added coverage for reasoning compatibility checks and provider mode-switch scenarios.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds shared reasoning-compatibility predicates and history guards for strict providers, updates DeepSeek, MiMo, and Z.ai request behavior, adds mode-switch safety checks with optional condensation, and renders compatibility warnings in chat.

Changes

Reasoning compatibility safeguards

Layer / File(s) Summary
Provider reasoning-history guards
src/api/providers/utils/..., src/api/providers/{deepseek,mimo,zai}.ts, src/api/providers/utils/__tests__/*
Strict-provider requests detect tool calls without reasoning_content, log warnings, and disable or adjust reasoning fields.
Mode-switch compatibility pre-check
src/shared/reasoning-mode-compatibility.ts, src/shared/__tests__/reasoning-mode-compatibility.spec.ts, src/core/webview/ClineProvider.ts, src/core/tools/SwitchModeTool.ts
Mode switches identify their source, compare providers, emit compatibility warnings, and optionally condense task context.
Compatibility warning message flow
packages/types/src/message.ts, webview-ui/src/components/chat/ChatRow.tsx, webview-ui/src/i18n/locales/en/chat.json
Adds the warning message type and renders localized mode/provider details in chat.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: awaiting-review

Suggested reviewers: edelauna, hannesrudolph

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the required template and omits the mandatory linked issue section. Add the required Related GitHub Issue section with Closes: #..., and restructure the PR body to match the template headings, especially Description and Test Procedure.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main fix: preventing DeepSeek 400 errors during direct mode switches.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.3)
src/core/webview/ClineProvider.ts

File contains syntax errors that prevent linting: Line 1543: unexpected token \; Line 1563: expected , but instead found Failed; Line 1563: expected , but instead found to; Line 1563: expected , but instead found persist; Line 1563: expected , but instead found mode; Line 1563: expected , but instead found switch; Line 1563: expected , but instead found (; Line 1563: expected , but instead found }; Line 1639: Illegal use of reserved keyword private as an identifier in strict mode; Line 1639: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 1640: expected , but instead found :; Line 1641: expected , but instead found options; Line 1641: expected , but instead found :; Line 1642: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 1668: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 1672: expected , but instead found :; Line 1672: E

... [truncated 28372 characters] ...

ted a semicolon or an implicit semicolon after a statement, but found none; Line 4030: Illegal return statement outside of a function; Line 4040: Illegal return statement outside of a function; Line 4043: Illegal return statement outside of a function; Line 4121: Illegal use of reserved keyword public as an identifier in strict mode; Line 4121: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 4121: expected , but instead found :; Line 4121: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 4128: Illegal return statement outside of a function; Line 4135: Illegal return statement outside of a function; Line 4144: Illegal return statement outside of a function; Line 4147: Expected a statement but instead found '}'.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/core/webview/ClineProvider.ts

Parsing error: Invalid character.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/core/webview/ClineProvider.ts (1)

1504-1505: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Redundant config lookups.

getModeConfigId(newMode) and listConfig() are called here and again at lines 1584-1585 for the same newMode shortly after. Consider hoisting/reusing a single lookup to avoid duplicate storage round-trips on every mode switch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/webview/ClineProvider.ts` around lines 1504 - 1505, In the
mode-switch flow of ClineProvider, avoid repeating the
providerSettingsManager.getModeConfigId(newMode) and listConfig() lookups
performed again near the later mode-handling block. Hoist the results or reuse
shared variables so both sections use one lookup per mode switch while
preserving the existing configuration-selection behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/providers/deepseek.ts`:
- Around line 19-20: Remove the duplicate historyHasToolCallsWithoutReasoning
import in the import section of deepseek.ts, retaining a single import from
./utils/reasoning-history-guard so compilation succeeds.

In `@src/api/providers/utils/__tests__/reasoning-history-guard.spec.ts`:
- Around line 53-62: Rename the test case describing the empty tool_calls
scenario to state that it returns false, while preserving the existing messages
setup and historyHasToolCallsWithoutReasoning assertion.

In `@src/core/webview/ClineProvider.ts`:
- Around line 1540-1545: Fix the template literal passed to this.log in the
mode-switch compatibility check catch block by using plain backticks rather than
escaped backticks, matching the valid pattern in the nearby catch block and
restoring valid parsing.
- Around line 1501-1546: Update the compatibility pre-check in the mode-switch
flow around modeSwitchRisksReasoningIncompatibility to honor
lockApiConfigAcrossModes before resolving or comparing the target profile
provider. When the setting prevents the API profile switch, skip both the
warning and optional task.condenseContext() so checks reflect the provider that
will actually remain active; preserve existing behavior when switching is
allowed.

In `@webview-ui/src/components/chat/ChatRow.tsx`:
- Around line 1580-1599: The mode_switch_compatibility_warning branch in ChatRow
lacks local Vitest coverage. Add tests under webview-ui/src/**/__tests__ that
render ChatRow with valid warning payloads and verify the translated WarningRow
title/message, including parsed mode/provider values and fallback behavior for
missing fields; keep the existing rendering logic unchanged.

---

Nitpick comments:
In `@src/core/webview/ClineProvider.ts`:
- Around line 1504-1505: In the mode-switch flow of ClineProvider, avoid
repeating the providerSettingsManager.getModeConfigId(newMode) and listConfig()
lookups performed again near the later mode-handling block. Hoist the results or
reuse shared variables so both sections use one lookup per mode switch while
preserving the existing configuration-selection behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ceb7260c-bf05-4f55-b32b-7f780cbbda82

📥 Commits

Reviewing files that changed from the base of the PR and between 7af1a8a and 7007cf4.

📒 Files selected for processing (12)
  • packages/types/src/message.ts
  • src/api/providers/deepseek.ts
  • src/api/providers/mimo.ts
  • src/api/providers/utils/__tests__/reasoning-history-guard.spec.ts
  • src/api/providers/utils/reasoning-history-guard.ts
  • src/api/providers/zai.ts
  • src/core/tools/SwitchModeTool.ts
  • src/core/webview/ClineProvider.ts
  • src/shared/__tests__/reasoning-mode-compatibility.spec.ts
  • src/shared/reasoning-mode-compatibility.ts
  • webview-ui/src/components/chat/ChatRow.tsx
  • webview-ui/src/i18n/locales/en/chat.json

Comment on lines +19 to +20
import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"
import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Duplicate import breaks the build.

historyHasToolCallsWithoutReasoning is imported twice from the same module. This is a duplicate identifier declaration that will fail TypeScript/esbuild compilation — matching the reported pipeline failure (esbuild.mjs --production exiting with status 1).

🐛 Proposed fix
 import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"
-import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"
import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"
import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/deepseek.ts` around lines 19 - 20, Remove the duplicate
historyHasToolCallsWithoutReasoning import in the import section of deepseek.ts,
retaining a single import from ./utils/reasoning-history-guard so compilation
succeeds.

Source: Pipeline failures

Comment on lines +53 to +62
it("returns true when assistant messages have empty tool_calls array", () => {
const messages = [
{
role: "assistant",
content: "hello",
tool_calls: [],
},
]
expect(historyHasToolCallsWithoutReasoning(messages)).toBe(false)
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Test title contradicts its assertion.

The test is titled "returns true..." but asserts .toBe(false) — the assertion is correct (empty tool_calls shouldn't trigger the guard), but the description is inverted and will confuse future readers.

✏️ Proposed fix
-	it("returns true when assistant messages have empty tool_calls array", () => {
+	it("returns false when assistant messages have empty tool_calls array", () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("returns true when assistant messages have empty tool_calls array", () => {
const messages = [
{
role: "assistant",
content: "hello",
tool_calls: [],
},
]
expect(historyHasToolCallsWithoutReasoning(messages)).toBe(false)
})
it("returns false when assistant messages have empty tool_calls array", () => {
const messages = [
{
role: "assistant",
content: "hello",
tool_calls: [],
},
]
expect(historyHasToolCallsWithoutReasoning(messages)).toBe(false)
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/utils/__tests__/reasoning-history-guard.spec.ts` around
lines 53 - 62, Rename the test case describing the empty tool_calls scenario to
state that it returns false, while preserving the existing messages setup and
historyHasToolCallsWithoutReasoning assertion.

Comment on lines +1501 to +1546
if (via !== "new_task_delegation") {
try {
const fromProviderName = task.apiConfiguration?.apiProvider
const toConfigId = await this.providerSettingsManager.getModeConfigId(newMode)
const listApiConfig = await this.providerSettingsManager.listConfig()
const toProfile = toConfigId
? listApiConfig.find((c) => c.id === toConfigId)
: undefined
const toProviderName = toProfile
? (await this.providerSettingsManager.getProfile({ name: toProfile.name })).apiProvider
: fromProviderName

if (
toProviderName &&
modeSwitchRisksReasoningIncompatibility(fromProviderName, toProviderName)
) {
// Behavior A — always show visible warning in chat
await task.say(
"mode_switch_compatibility_warning",
JSON.stringify({
fromMode,
toMode: newMode,
fromProvider: fromProviderName,
toProvider: toProviderName,
via,
}),
undefined,
undefined,
undefined,
undefined,
{ isNonInteractive: true },
)

// Behavior B — optional auto-condense when setting is enabled
const autoCondense = this.context.workspaceState.get("autoCondenseOnRiskyModeSwitch", false)
if (autoCondense) {
await task.condenseContext()
}
}
} catch (innerError) {
// Non-fatal: log but don't block the mode switch if the check fails.
this.log(
\`Mode-switch compatibility check failed: ${innerError instanceof Error ? innerError.message : String(innerError)}\`,
)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Compatibility pre-check ignores lockApiConfigAcrossModes.

The guard resolves toProviderName from the target mode's saved profile and can fire the warning (and optionally auto-condense context) even when the provider will never actually change, because lockApiConfigAcrossModes (checked later, at line 1577) prevents the profile switch from being applied at all. This produces spurious warnings and can trigger an unnecessary context condensation for users who lock their API config across modes.

🛠️ Proposed fix
 			if (via !== "new_task_delegation") {
 				try {
+					const lockApiConfigAcrossModes = this.context.workspaceState.get("lockApiConfigAcrossModes", false)
 					const fromProviderName = task.apiConfiguration?.apiProvider
-					const toConfigId = await this.providerSettingsManager.getModeConfigId(newMode)
-					const listApiConfig = await this.providerSettingsManager.listConfig()
-					const toProfile = toConfigId
-						? listApiConfig.find((c) => c.id === toConfigId)
-						: undefined
-					const toProviderName = toProfile
-						? (await this.providerSettingsManager.getProfile({ name: toProfile.name })).apiProvider
-						: fromProviderName
+					const toConfigId = lockApiConfigAcrossModes
+						? undefined
+						: await this.providerSettingsManager.getModeConfigId(newMode)
+					const listApiConfig = await this.providerSettingsManager.listConfig()
+					const toProfile = toConfigId
+						? listApiConfig.find((c) => c.id === toConfigId)
+						: undefined
+					const toProviderName = toProfile
+						? (await this.providerSettingsManager.getProfile({ name: toProfile.name })).apiProvider
+						: fromProviderName
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (via !== "new_task_delegation") {
try {
const fromProviderName = task.apiConfiguration?.apiProvider
const toConfigId = await this.providerSettingsManager.getModeConfigId(newMode)
const listApiConfig = await this.providerSettingsManager.listConfig()
const toProfile = toConfigId
? listApiConfig.find((c) => c.id === toConfigId)
: undefined
const toProviderName = toProfile
? (await this.providerSettingsManager.getProfile({ name: toProfile.name })).apiProvider
: fromProviderName
if (
toProviderName &&
modeSwitchRisksReasoningIncompatibility(fromProviderName, toProviderName)
) {
// Behavior A — always show visible warning in chat
await task.say(
"mode_switch_compatibility_warning",
JSON.stringify({
fromMode,
toMode: newMode,
fromProvider: fromProviderName,
toProvider: toProviderName,
via,
}),
undefined,
undefined,
undefined,
undefined,
{ isNonInteractive: true },
)
// Behavior B — optional auto-condense when setting is enabled
const autoCondense = this.context.workspaceState.get("autoCondenseOnRiskyModeSwitch", false)
if (autoCondense) {
await task.condenseContext()
}
}
} catch (innerError) {
// Non-fatal: log but don't block the mode switch if the check fails.
this.log(
\`Mode-switch compatibility check failed: ${innerError instanceof Error ? innerError.message : String(innerError)}\`,
)
}
}
if (via !== "new_task_delegation") {
try {
const lockApiConfigAcrossModes = this.context.workspaceState.get("lockApiConfigAcrossModes", false)
const fromProviderName = task.apiConfiguration?.apiProvider
const toConfigId = lockApiConfigAcrossModes
? undefined
: await this.providerSettingsManager.getModeConfigId(newMode)
const listApiConfig = await this.providerSettingsManager.listConfig()
const toProfile = toConfigId
? listApiConfig.find((c) => c.id === toConfigId)
: undefined
const toProviderName = toProfile
? (await this.providerSettingsManager.getProfile({ name: toProfile.name })).apiProvider
: fromProviderName
if (
toProviderName &&
modeSwitchRisksReasoningIncompatibility(fromProviderName, toProviderName)
) {
// Behavior A — always show visible warning in chat
await task.say(
"mode_switch_compatibility_warning",
JSON.stringify({
fromMode,
toMode: newMode,
fromProvider: fromProviderName,
toProvider: toProviderName,
via,
}),
undefined,
undefined,
undefined,
undefined,
{ isNonInteractive: true },
)
// Behavior B — optional auto-condense when setting is enabled
const autoCondense = this.context.workspaceState.get("autoCondenseOnRiskyModeSwitch", false)
if (autoCondense) {
await task.condenseContext()
}
}
} catch (innerError) {
// Non-fatal: log but don't block the mode switch if the check fails.
this.log(
\`Mode-switch compatibility check failed: ${innerError instanceof Error ? innerError.message : String(innerError)}\`,
)
}
}
🧰 Tools
🪛 Biome (2.5.3)

[error] 1543-1543: unexpected token \

(parse)

🪛 GitHub Actions: E2E Tests (Mocked) / 0_e2e-mock.txt

[error] 1543-1543: esbuild syntax error: Syntax error "". Build failed during pnpm run bundle` (node esbuild.mjs).

🪛 GitHub Actions: E2E Tests (Mocked) / e2e-mock

[error] 1543-1543: esbuild failed with Syntax error "`" at core/webview/ClineProvider.ts:1543:7.

🪛 GitHub Actions: Release Validation / 0_validate-release.txt

[error] 1543-1543: esbuild syntax error: Syntax error "`". Build failed with 1 error.

🪛 GitHub Actions: Release Validation / validate-release

[error] 1543-1543: esbuild failed while bundling with a syntax error: "Syntax error `" (ERROR at core/webview/ClineProvider.ts:1543:7). Command: "node esbuild.mjs --production"

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/webview/ClineProvider.ts` around lines 1501 - 1546, Update the
compatibility pre-check in the mode-switch flow around
modeSwitchRisksReasoningIncompatibility to honor lockApiConfigAcrossModes before
resolving or comparing the target profile provider. When the setting prevents
the API profile switch, skip both the warning and optional
task.condenseContext() so checks reflect the provider that will actually remain
active; preserve existing behavior when switching is allowed.

Comment on lines +1540 to +1545
} catch (innerError) {
// Non-fatal: log but don't block the mode switch if the check fails.
this.log(
\`Mode-switch compatibility check failed: ${innerError instanceof Error ? innerError.message : String(innerError)}\`,
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Escaped backticks produce a syntax error.

The this.log template literal is wrapped in escaped backticks (\`) instead of plain backticks, unlike the identical pattern two lines below at 1562-1564. This matches the Biome parse error reported at line 1543 and is very likely the (or one of the) cause(s) of the "esbuild --production" bundle failure in the pipeline.

🐛 Proposed fix
 				} catch (innerError) {
 					// Non-fatal: log but don't block the mode switch if the check fails.
 					this.log(
-						\`Mode-switch compatibility check failed: ${innerError instanceof Error ? innerError.message : String(innerError)}\`,
+						`Mode-switch compatibility check failed: ${innerError instanceof Error ? innerError.message : String(innerError)}`,
 					)
 				}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (innerError) {
// Non-fatal: log but don't block the mode switch if the check fails.
this.log(
\`Mode-switch compatibility check failed: ${innerError instanceof Error ? innerError.message : String(innerError)}\`,
)
}
} catch (innerError) {
// Non-fatal: log but don't block the mode switch if the check fails.
this.log(
`Mode-switch compatibility check failed: ${innerError instanceof Error ? innerError.message : String(innerError)}`,
)
}
🧰 Tools
🪛 Biome (2.5.3)

[error] 1543-1543: unexpected token \

(parse)

🪛 GitHub Actions: E2E Tests (Mocked) / 0_e2e-mock.txt

[error] 1543-1543: esbuild syntax error: Syntax error "". Build failed during pnpm run bundle` (node esbuild.mjs).

🪛 GitHub Actions: E2E Tests (Mocked) / e2e-mock

[error] 1543-1543: esbuild failed with Syntax error "`" at core/webview/ClineProvider.ts:1543:7.

🪛 GitHub Actions: Release Validation / 0_validate-release.txt

[error] 1543-1543: esbuild syntax error: Syntax error "`". Build failed with 1 error.

🪛 GitHub Actions: Release Validation / validate-release

[error] 1543-1543: esbuild failed while bundling with a syntax error: "Syntax error `" (ERROR at core/webview/ClineProvider.ts:1543:7). Command: "node esbuild.mjs --production"

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/webview/ClineProvider.ts` around lines 1540 - 1545, Fix the template
literal passed to this.log in the mode-switch compatibility check catch block by
using plain backticks rather than escaped backticks, matching the valid pattern
in the nearby catch block and restoring valid parsing.

Sources: Linters/SAST tools, Pipeline failures

Comment on lines +1580 to +1599
case "mode_switch_compatibility_warning": {
const warningData = safeJsonParse<{
fromMode?: string
toMode?: string
fromProvider?: string
toProvider?: string
via?: string
}>(message.text || "{}")
if (!warningData) return null
return (
<WarningRow
title={t("chat:modeSwitchCompatibilityWarning.title")}
message={t("chat:modeSwitchCompatibilityWarning.message", {
fromMode: warningData.fromMode ?? "?",
toMode: warningData.toMode ?? "?",
toProvider: warningData.toProvider ?? "?",
})}
/>
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Rendering logic is correct; add local test coverage.

The parsing/rendering itself matches the established pattern and WarningRowProps contract. No Vitest coverage for this new mode_switch_compatibility_warning branch is included in this batch.

As per coding guidelines, "Prefer local webview-ui tests for React/webview behavior such as component rendering... Add or update Vitest coverage under webview-ui/src/**/__tests__ instead of reaching for apps/vscode-e2e."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webview-ui/src/components/chat/ChatRow.tsx` around lines 1580 - 1599, The
mode_switch_compatibility_warning branch in ChatRow lacks local Vitest coverage.
Add tests under webview-ui/src/**/__tests__ that render ChatRow with valid
warning payloads and verify the translated WarningRow title/message, including
parsed mode/provider values and fallback behavior for missing fields; keep the
existing rendering logic unchanged.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant