Merge pull request #12950 from Kilo-Org/fix/tui-disposal-abort-log

fix(tui): suppress expected disposal aborts
This commit is contained in:
Joshua Lambert
2026-08-06 10:08:17 -04:00
committed by GitHub
3 changed files with 86 additions and 15 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Avoid printing an error when closing the TUI cancels in-flight startup refreshes.
+36 -14
View File
@@ -64,6 +64,23 @@ export function eventLocation(metadata: { directory: string; workspace?: string
}
// kilocode_change end
// kilocode_change start - suppress only refreshes canceled by normal TUI disposal
export function shouldReportDefaultLocationFailure(reason: unknown, disposed: boolean) {
if (!disposed) return true
return !(typeof reason === "object" && reason !== null && "name" in reason && reason.name === "AbortError")
}
export async function reportDefaultLocationFailure(
promise: Promise<void>,
disposed: () => boolean,
report: (reason: unknown) => void = (reason) => console.error("Failed to refresh default location data", reason),
) {
return promise.catch((reason) => {
if (shouldReportDefaultLocationFailure(reason, disposed())) report(reason)
})
}
// kilocode_change end
export const { use: useData, provider: DataProvider } = createSimpleContext({
name: "Data",
init: () => {
@@ -575,21 +592,26 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
},
}
onMount(() => {
void Promise.allSettled([
result.location.refresh(),
result.location.agent.refresh(),
result.location.integration.refresh(),
result.location.model.refresh(),
result.location.provider.refresh(),
result.location.reference.refresh(),
result.location.command.refresh(),
result.location.skill.refresh(),
]).then((settled) => {
for (const failure of settled.filter((item) => item.status === "rejected"))
console.error("Failed to refresh default location data", failure.reason)
})
// kilocode_change start - classify each rejection when it occurs so later disposal cannot hide earlier failures
let disposed = false
onCleanup(() => {
disposed = true
})
// kilocode_change end
// kilocode_change start
onMount(() => {
void Promise.all([
reportDefaultLocationFailure(result.location.refresh(), () => disposed),
reportDefaultLocationFailure(result.location.agent.refresh(), () => disposed),
reportDefaultLocationFailure(result.location.integration.refresh(), () => disposed),
reportDefaultLocationFailure(result.location.model.refresh(), () => disposed),
reportDefaultLocationFailure(result.location.provider.refresh(), () => disposed),
reportDefaultLocationFailure(result.location.reference.refresh(), () => disposed),
reportDefaultLocationFailure(result.location.command.refresh(), () => disposed),
reportDefaultLocationFailure(result.location.skill.refresh(), () => disposed),
])
})
// kilocode_change end
return result
},
+45 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { eventLocation } from "../../src/context/data"
import { eventLocation, reportDefaultLocationFailure, shouldReportDefaultLocationFailure } from "../../src/context/data"
describe("eventLocation", () => {
test("uses the default location for global events", () => {
@@ -13,3 +13,47 @@ describe("eventLocation", () => {
})
})
})
describe("shouldReportDefaultLocationFailure", () => {
test("suppresses lifecycle aborts after disposal", () => {
expect(shouldReportDefaultLocationFailure(new DOMException("aborted", "AbortError"), true)).toBe(false)
})
test("suppresses cross-realm-shaped lifecycle aborts after disposal", () => {
expect(shouldReportDefaultLocationFailure({ name: "AbortError" }, true)).toBe(false)
})
test("reports aborts while mounted", () => {
expect(shouldReportDefaultLocationFailure(new DOMException("aborted", "AbortError"), false)).toBe(true)
})
test("reports non-abort failures after disposal", () => {
expect(shouldReportDefaultLocationFailure(new Error("network failed"), true)).toBe(true)
})
})
describe("reportDefaultLocationFailure", () => {
test("reports a mounted abort immediately even if disposal happens before other refreshes settle", async () => {
let disposed = false
const reports: unknown[] = []
const abort = Promise.reject(new DOMException("aborted", "AbortError"))
const pending = Promise.withResolvers<void>()
const first = reportDefaultLocationFailure(
abort,
() => disposed,
(reason) => reports.push(reason),
)
await first
disposed = true
pending.reject(new DOMException("aborted", "AbortError"))
await reportDefaultLocationFailure(
pending.promise,
() => disposed,
(reason) => reports.push(reason),
)
expect(reports).toHaveLength(1)
expect(reports[0]).toBeInstanceOf(DOMException)
})
})