From 5ae91eb091edaba8c674eeff6ad9dfdde19e76d5 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 31 Jul 2026 13:21:15 +0200 Subject: [PATCH 1/2] fix(cli): handle missing nested config unsets --- .changeset/quiet-config-unset.md | 5 +++++ packages/opencode/src/kilocode/config/writer.ts | 4 ++++ .../test/kilocode/server/config-overlay.test.ts | 12 ++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 .changeset/quiet-config-unset.md diff --git a/.changeset/quiet-config-unset.md b/.changeset/quiet-config-unset.md new file mode 100644 index 00000000000..2a15a23e18a --- /dev/null +++ b/.changeset/quiet-config-unset.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Allow clearing a nested project setting when no project config file exists yet. diff --git a/packages/opencode/src/kilocode/config/writer.ts b/packages/opencode/src/kilocode/config/writer.ts index eee4a277111..59c47dcdb53 100644 --- a/packages/opencode/src/kilocode/config/writer.ts +++ b/packages/opencode/src/kilocode/config/writer.ts @@ -82,6 +82,10 @@ export namespace KilocodeConfigWriter { function patchJsonc(input: string, patch: unknown, parts: string[] = []): string { if (!isRecord(patch)) { + if (patch === null) { + const tree = parseTree(input) + if (!tree || !findNodeAtLocation(tree, parts)) return input + } return applyEdits( input, modify(input, parts, patch === null ? undefined : patch, { diff --git a/packages/opencode/test/kilocode/server/config-overlay.test.ts b/packages/opencode/test/kilocode/server/config-overlay.test.ts index 134dede6baf..3d7929bdb63 100644 --- a/packages/opencode/test/kilocode/server/config-overlay.test.ts +++ b/packages/opencode/test/kilocode/server/config-overlay.test.ts @@ -103,6 +103,18 @@ describe("config overlay routes", () => { expect(await Bun.file(target.path).text()).toContain('"model": "test/model"') }) + test("ignores a nested unset path when the project target is missing", async () => { + await using project = await tmpdir() + const response = await req(project.path, "/config/overlay", { + method: "PATCH", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ scope: "project", unset: [["agent", "explore", "model"]] }), + }) + + expect(response.status).toBe(200) + expect(await Bun.file(path.join(project.path, ".kilo", "kilo.jsonc")).exists()).toBe(false) + }) + test("returns exact raw target data and a stable missing-file revision", async () => { await using project = await tmpdir() const first = await KilocodeConfigOverlay.target({ scope: "project", directory: project.path }) From 899934ecf4680485d16c1268a10a89b4b584dfb0 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 31 Jul 2026 13:30:59 +0200 Subject: [PATCH 2/2] test(cli): cover nested config unset behavior --- .../opencode/src/kilocode/config/writer.ts | 1 + .../kilocode/server/config-overlay.test.ts | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/packages/opencode/src/kilocode/config/writer.ts b/packages/opencode/src/kilocode/config/writer.ts index 59c47dcdb53..162ee82126e 100644 --- a/packages/opencode/src/kilocode/config/writer.ts +++ b/packages/opencode/src/kilocode/config/writer.ts @@ -83,6 +83,7 @@ export namespace KilocodeConfigWriter { function patchJsonc(input: string, patch: unknown, parts: string[] = []): string { if (!isRecord(patch)) { if (patch === null) { + // jsonc-parser cannot delete a nested path when its parent is absent. const tree = parseTree(input) if (!tree || !findNodeAtLocation(tree, parts)) return input } diff --git a/packages/opencode/test/kilocode/server/config-overlay.test.ts b/packages/opencode/test/kilocode/server/config-overlay.test.ts index 3d7929bdb63..28cf2ae86c5 100644 --- a/packages/opencode/test/kilocode/server/config-overlay.test.ts +++ b/packages/opencode/test/kilocode/server/config-overlay.test.ts @@ -115,6 +115,29 @@ describe("config overlay routes", () => { expect(await Bun.file(path.join(project.path, ".kilo", "kilo.jsonc")).exists()).toBe(false) }) + test("removes an existing nested unset path", async () => { + await using project = await tmpdir() + const file = path.join(project.path, ".kilo", "kilo.jsonc") + await Filesystem.write( + file, + '{\n "$schema": "https://app.kilo.ai/config.json",\n "indexing": {\n "enabled": false,\n "provider": "ollama",\n "ollama": { "baseUrl": "http://127.0.0.1:11434" }\n }\n}\n', + ) + + const response = await req(project.path, "/config/overlay", { + method: "PATCH", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ scope: "project", unset: [["indexing", "enabled"]] }), + }) + expect(response.status).toBe(200) + + const saved = (await Bun.file(file).json()) as { + indexing: { enabled?: boolean; provider: string; ollama: { baseUrl: string } } + } + expect(saved.indexing.enabled).toBeUndefined() + expect(saved.indexing.provider).toBe("ollama") + expect(saved.indexing.ollama.baseUrl).toBe("http://127.0.0.1:11434") + }) + test("returns exact raw target data and a stable missing-file revision", async () => { await using project = await tmpdir() const first = await KilocodeConfigOverlay.target({ scope: "project", directory: project.path })