fix(server): return diagnosable body for schema rejections (#26631)

This commit is contained in:
Kit Langton
2026-05-10 16:21:32 +00:00
committed by GitHub
parent cc2915be16
commit 049502fac6
10 changed files with 606 additions and 324 deletions
@@ -0,0 +1,36 @@
import { Effect } from "effect"
import { HttpServerResponse } from "effect/unstable/http"
import { HttpApiMiddleware } from "effect/unstable/httpapi"
import * as Log from "@opencode-ai/core/util/log"
const log = Log.create({ service: "server" })
// Effect's Issue formatter recursively dumps the rejected `actual` value with
// no truncation, so a 5KB invalid array produces a ~360KB string. Cap to keep
// 4xx responses small and avoid mirroring entire request payloads (which may
// contain secrets) into the response body and log file.
const REASON_LIMIT = 1024
function truncateReason(reason: string) {
if (reason.length <= REASON_LIMIT) return reason
return reason.slice(0, REASON_LIMIT) + `… (${reason.length - REASON_LIMIT} more chars)`
}
// Default Respondable returns an empty 400 body. Match the NamedError shape
// used by other 4xx/5xx so the SDK's `wrapClientError` extracts `.data.message`.
export class SchemaErrorMiddleware extends HttpApiMiddleware.Service<SchemaErrorMiddleware>()(
"@opencode/HttpApiSchemaError",
) {}
export const schemaErrorLayer = HttpApiMiddleware.layerSchemaErrorTransform(
SchemaErrorMiddleware,
(error) => {
const reason = truncateReason(error.cause.message)
log.warn("schema rejection", { kind: error.kind, reason })
return Effect.succeed(
HttpServerResponse.jsonUnsafe(
{ name: "BadRequest", data: { message: reason, kind: error.kind } },
{ status: 400 },
),
)
},
)