fix(vscode): allow clearing agent model override by sending null

Clearing the Model Override field for native agents (e.g. Explore,
General) sent the patch as `{ model: undefined }`. JSON.stringify drops
undefined, so the backend received an empty patch, the override stayed
in kilo.json, and the next configUpdated push repopulated the field.

Send `null` instead — the backend's patchJsonc already treats null as
a delete sentinel (matches the pattern already used in ModelsTab and
AutoApproveTab). Fixes #9527.
This commit is contained in:
kiloconnect[bot]
2026-04-27 09:18:42 +00:00
parent 6965d56918
commit 3f0bf322c1
3 changed files with 55 additions and 1 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Fix clearing an agent's Model Override in Agent Behaviour settings. Previously, clearing the field and saving would repopulate the old value because the empty input was sent as `undefined` and dropped by `JSON.stringify`, so the backend never received a delete instruction. The field now reverts to the global default model as expected.
@@ -155,4 +155,53 @@ describe("ConfigState", () => {
expect(s.config.snapshot).toBe(true)
expect(s.dirty).toBe(false)
})
// -------------------------------------------------------------------------
// Issue #9527: clearing an agent model override must unset it, not repopulate
// -------------------------------------------------------------------------
describe("clearing an agent model override (issue #9527)", () => {
it("keeps null in the draft so the backend receives a delete sentinel", () => {
const s = new ConfigState()
s.handleConfigLoaded({ agent: { explore: { model: "anthropic/claude-sonnet-4-20250514" } } })
// User clears the Model Override field. ModeEditView now sends `null`
// instead of `undefined` (the fix). null is the delete sentinel that
// patchJsonc maps to jsonc-parser's remove operation.
s.updateConfig({ agent: { explore: { model: null } } })
// Optimistic UI: stripNulls removes the key so the field renders empty.
expect(s.config.agent?.explore?.model).toBeUndefined()
expect(s.dirty).toBe(true)
// Draft must retain the null so it survives JSON.stringify on the wire
// and reaches patchJsonc as an explicit delete.
expect(s.draft.agent?.explore?.model).toBeNull()
expect(JSON.parse(JSON.stringify(s.draft))).toEqual({
agent: { explore: { model: null } },
})
})
it("undefined (the old buggy behavior) is dropped by JSON.stringify", () => {
// Reproduction of the pre-fix bug: sending `undefined` results in an
// empty patch on the wire, so the backend never deletes the override
// and the next configUpdated pushes the stale model back into the UI.
const draft = { agent: { explore: { model: undefined } } }
expect(JSON.parse(JSON.stringify(draft))).toEqual({ agent: { explore: {} } })
})
it("confirms the save and drops the draft once the backend acks", () => {
const s = new ConfigState()
s.handleConfigLoaded({ agent: { explore: { model: "anthropic/claude-sonnet-4-20250514" } } })
s.updateConfig({ agent: { explore: { model: null } } })
s.saveConfig()
// Backend removed the override and pushes the stripped config back.
s.handleConfigUpdated({ agent: { explore: {} } })
expect(s.config.agent?.explore?.model).toBeUndefined()
expect(s.dirty).toBe(false)
expect(s.saving).toBe(false)
expect(Object.keys(s.draft).length).toBe(0)
})
})
})
@@ -145,7 +145,7 @@ const ModeEditView: Component<Props> = (props) => {
<TextField
value={cfg().model ?? ""}
placeholder="e.g. anthropic/claude-sonnet-4-20250514"
onChange={(val) => update({ model: val || undefined })}
onChange={(val) => update({ model: val || null })}
/>
</SettingsRow>