fix(tui): show config error details on startup (#27803)
This commit is contained in:
@@ -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", () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NodeHttpServer, NodeServices } from "@effect/platform-node"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { describe, expect } from "bun:test"
|
||||
import { ConfigError } from "../../src/config/error"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { HttpClient, HttpClientRequest, HttpRouter } from "effect/unstable/http"
|
||||
import { errorLayer } from "../../src/server/routes/instance/httpapi/middleware/error"
|
||||
@@ -50,6 +51,27 @@ describe("HttpApi error middleware", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("preserves config defects as client-visible bad requests", () =>
|
||||
Effect.gen(function* () {
|
||||
const configError = new ConfigError.InvalidError({
|
||||
path: "/tmp/opencode.json",
|
||||
issues: [{ message: "Expected object", path: ["provider", "anthropic", "options"] }],
|
||||
})
|
||||
|
||||
yield* HttpRouter.add("GET", "/config-error", Effect.die(configError)).pipe(
|
||||
Layer.provide(errorLayer),
|
||||
HttpRouter.serve,
|
||||
Layer.build,
|
||||
)
|
||||
|
||||
const response = yield* HttpClientRequest.get("/config-error").pipe(HttpClient.execute)
|
||||
const body = yield* response.json
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
expect(JSON.stringify(body)).toBe(JSON.stringify(configError.toObject()))
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("does not map storage not-found defects to 404", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* HttpRouter.add(
|
||||
|
||||
Reference in New Issue
Block a user