fix(tui): show config error details on startup (#27803)

This commit is contained in:
Kit Langton
2026-05-15 20:42:56 -04:00
committed by GitHub
parent 2385123f03
commit 5911bd532d
6 changed files with 185 additions and 18 deletions

View File

@@ -5,6 +5,7 @@
*/
import { describe, expect, test } from "bun:test"
import { aggregateFailures } from "@/cli/cmd/tui/context/aggregate-failures"
import { ConfigError } from "@/config/error"
describe("aggregateFailures", () => {
test("returns null when every result is fulfilled", () => {
@@ -41,11 +42,50 @@ describe("aggregateFailures", () => {
expect(err!.message).toContain("agents: boom")
})
test("formats structured config errors hidden inside SDK error causes", () => {
const configError = new ConfigError.InvalidError({
path: "/tmp/opencode.json",
issues: [{ message: "Expected object", path: ["provider", "anthropic", "options"] }],
})
const err = aggregateFailures([
{
name: "config.get",
result: {
status: "rejected",
reason: new Error("ConfigInvalidError", {
cause: {
body: configError.toObject(),
},
}),
},
},
])
expect(err!.message).toContain("config.get: Configuration is invalid at /tmp/opencode.json")
expect(err!.message).toContain("Expected object provider.anthropic.options")
})
test("deduplicates identical failure messages across startup requests", () => {
const reason = new Error("same config problem")
const err = aggregateFailures([
{ name: "config.providers", result: { status: "rejected", reason } },
{ name: "provider.list", result: { status: "rejected", reason } },
{ name: "app.agents", result: { status: "rejected", reason } },
{ name: "config.get", result: { status: "rejected", reason } },
{ name: "project.sync", result: { status: "fulfilled", value: undefined } },
])
expect(err!.message).toContain("4 of 5 requests failed: same config problem")
expect(err!.message).toContain(
"Affected startup requests: config.providers, provider.list, app.agents, config.get",
)
expect(err!.message.match(/same config problem/g)?.length).toBe(1)
})
test("attaches structured failure list under .cause", () => {
const reason = new Error("nope")
const err = aggregateFailures([{ name: "providers", result: { status: "rejected", reason } }])
const cause = err!.cause as { failures: Array<{ name: string; reason: unknown }> }
expect(cause.failures).toEqual([{ name: "providers", reason }])
expect(err!.cause).toEqual({ failures: [{ name: "providers", reason }] })
})
test("falls back to String() for opaque reasons", () => {