Merge pull request #8972 from Kilo-Org/kilocode-auto-approve-permissions

fix(cli): fix disable auto-approve leaving stale config and UI
This commit is contained in:
Marian Alexandru Alecu
2026-04-15 16:49:39 +03:00
committed by GitHub
3 changed files with 60 additions and 8 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 null values and drop objects left empty after removal. */
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"
@@ -54,6 +56,7 @@ export const PermissionKilocodeRoutes = lazy(() =>
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({
@@ -1,13 +1,60 @@
// kilocode_change - new file
import { describe, expect, test } from "bun:test"
import { Permission } from "../../src/permission"
import { PermissionID } from "../../src/permission/schema"
import { Instance } from "../../src/project/instance"
import { Server } from "../../src/server/server"
import { Session } from "../../src/session"
import { tmpdir } from "../fixture/fixture"
import { Permission } from "../../../src/permission"
import { PermissionID } from "../../../src/permission/schema"
import { Instance } from "../../../src/project/instance"
import { Server } from "../../../src/server/server"
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 })