diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts index 553e4de9685c..9b791ac8077e 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts @@ -1,3 +1,4 @@ +import { ConfigError } from "@/config/error" import { NamedError } from "@opencode-ai/core/util/error" import * as Log from "@opencode-ai/core/util/log" import { Cause, Effect } from "effect" @@ -18,6 +19,10 @@ export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) if (!defect) return Effect.failCause(cause) const error = defect.defect + if (ConfigError.JsonError.isInstance(error) || ConfigError.InvalidError.isInstance(error)) { + return Effect.succeed(HttpServerResponse.jsonUnsafe(error.toObject(), { status: 400 })) + } + const ref = `err_${crypto.randomUUID().slice(0, 8)}` log.error("failed", { ref, error, cause: Cause.pretty(cause) }) diff --git a/packages/opencode/test/server/httpapi-error-middleware.test.ts b/packages/opencode/test/server/httpapi-error-middleware.test.ts index 84ce7c8f8a5d..7c1ef5830129 100644 --- a/packages/opencode/test/server/httpapi-error-middleware.test.ts +++ b/packages/opencode/test/server/httpapi-error-middleware.test.ts @@ -53,7 +53,7 @@ describe("HttpApi error middleware", () => { }), ) - it.live("does not expose config defects from generic middleware", () => + it.live("returns config validation defects for startup requests", () => Effect.gen(function* () { const configError = new ConfigError.InvalidError({ path: "/tmp/opencode.json", @@ -68,13 +68,42 @@ describe("HttpApi error middleware", () => { const response = yield* HttpClientRequest.get("/config-error").pipe(HttpClient.execute) const body = yield* response.json - const serialized = JSON.stringify(body) - expect(response.status).toBe(500) - expectUnknownErrorBody(body) - expect(serialized).not.toContain("/tmp/opencode.json") - expect(serialized).not.toContain("provider") - expect(serialized).not.toContain("anthropic") + expect(response.status).toBe(400) + expect(body).toEqual({ + name: "ConfigInvalidError", + data: { + path: "/tmp/opencode.json", + issues: [{ message: "Expected object", path: ["provider", "anthropic", "options"] }], + }, + }) + }), + ) + + it.live("returns config json defects for startup requests", () => + Effect.gen(function* () { + const configError = new ConfigError.JsonError({ + path: "/tmp/opencode.jsonc", + message: "ValueExpected at line 3, column 1", + }) + + yield* HttpRouter.add("GET", "/config-json-error", Effect.die(configError)).pipe( + Layer.provide(errorLayer), + HttpRouter.serve, + Layer.build, + ) + + const response = yield* HttpClientRequest.get("/config-json-error").pipe(HttpClient.execute) + const body = yield* response.json + + expect(response.status).toBe(400) + expect(body).toEqual({ + name: "ConfigJsonError", + data: { + path: "/tmp/opencode.jsonc", + message: "ValueExpected at line 3, column 1", + }, + }) }), )