fix(cli): fix disable auto-approve leaving stale config and UI

This commit is contained in:
Alex Alecu
2026-04-15 14:09:24 +03:00
parent 22806d4966
commit 624be2310d
3 changed files with 55 additions and 3 deletions
@@ -292,13 +292,14 @@ export namespace KilocodeConfig {
// ── Config merge utilities ───────────────────────────────────────────
/** Recursively remove keys whose value is null (used after mergeDeep to honor delete sentinels). */
/** Recursively remove keys whose value is null or becomes an empty object after stripping. */
export function stripNulls(obj: Record<string, unknown>): Record<string, unknown> {
const result: Record<string, unknown> = {}
for (const [key, value] of Object.entries(obj)) {
if (value === null) continue
if (isRecord(value)) {
result[key] = stripNulls(value)
const stripped = stripNulls(value)
if (Object.keys(stripped).length > 0) result[key] = stripped
} else {
result[key] = value
}
@@ -1,10 +1,12 @@
import { Hono } from "hono"
import { describeRoute, resolver, validator } from "hono-openapi"
import z from "zod"
import { Bus } from "@/bus"
import { Config } from "@/config/config"
import { Permission } from "@/permission"
import { Session } from "@/session"
import { SessionID } from "@/session/schema" // kilocode_change
import { Event } from "../../server/event"
import { errors } from "../../server/error"
import { lazy } from "../../util/lazy"
@@ -52,8 +54,9 @@ export const PermissionKilocodeRoutes = lazy(() =>
return c.json(true)
}
await Config.updateGlobal({ permission: { "*": { "*": null } } }, { dispose: false })
await Config.updateGlobal({ permission: { "*": null } }, { dispose: false })
await Permission.allowEverything({ enable: false })
await Bus.publish(Event.ConfigUpdated, {})
return c.json(true)
}
@@ -65,6 +68,7 @@ export const PermissionKilocodeRoutes = lazy(() =>
})
} else {
await Config.updateGlobal({ permission: Permission.toConfig(rules) }, { dispose: false })
await Bus.publish(Event.ConfigUpdated, {})
}
await Permission.allowEverything({
@@ -8,6 +8,53 @@ import { Session } from "../../src/session"
import { tmpdir } from "../fixture/fixture"
describe("permission.allowEverything endpoint", () => {
test("disables global allow-all and removes wildcard from config", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const app = Server.Default().app
// Enable global auto-approve
const enable = await app.request("/permission/allow-everything", {
method: "POST",
headers: { "Content-Type": "application/json", "x-kilo-directory": tmp.path },
body: JSON.stringify({ enable: true }),
})
expect(enable.status).toBe(200)
// Disable global auto-approve
const disable = await app.request("/permission/allow-everything", {
method: "POST",
headers: { "Content-Type": "application/json", "x-kilo-directory": tmp.path },
body: JSON.stringify({ enable: false }),
})
expect(disable.status).toBe(200)
expect(await disable.json()).toBe(true)
// After disabling, permission requests should not be auto-approved
const session = await Session.create({})
const pending = Permission.ask({
id: PermissionID.make("permission_global_disable"),
sessionID: session.id,
permission: "bash",
patterns: ["ls"],
metadata: {},
always: [],
ruleset: [],
})
await Permission.reply({
requestID: PermissionID.make("permission_global_disable"),
reply: "reject",
})
await expect(pending).rejects.toBeInstanceOf(Permission.RejectedError)
},
})
})
test("disables session-scoped allow-all without touching global config", async () => {
await using tmp = await tmpdir({ git: true })