Skip to content

Commit d431a0e

Browse files
authored
fix: ensure effect server middleware properly parses errors (#25717)
1 parent 5720883 commit d431a0e

2 files changed

Lines changed: 60 additions & 0 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Provider } from "@/provider/provider"
2+
import { Session } from "@/session/session"
3+
import { NotFoundError } from "@/storage/storage"
4+
import { iife } from "@/util/iife"
5+
import { NamedError } from "@opencode-ai/core/util/error"
6+
import * as Log from "@opencode-ai/core/util/log"
7+
import { Cause, Effect } from "effect"
8+
import { HttpRouter, HttpServerError, HttpServerRespondable, HttpServerResponse } from "effect/unstable/http"
9+
10+
const log = Log.create({ service: "server" })
11+
12+
// Keep typed HttpApi failures on their declared error path; this boundary only replaces defect-only empty 500s.
13+
export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) =>
14+
effect.pipe(
15+
Effect.catchCause((cause) => {
16+
const defect = cause.reasons.filter(Cause.isDieReason).find((reason) => {
17+
if (HttpServerResponse.isHttpServerResponse(reason.defect)) return false
18+
if (HttpServerError.isHttpServerError(reason.defect)) return false
19+
if (HttpServerRespondable.isRespondable(reason.defect)) return false
20+
return true
21+
})
22+
if (!defect) return Effect.failCause(cause)
23+
24+
const error = defect.defect
25+
log.error("failed", { error, cause: Cause.pretty(cause) })
26+
27+
if (error instanceof NamedError) {
28+
return Effect.succeed(
29+
HttpServerResponse.jsonUnsafe(error.toObject(), {
30+
status: iife(() => {
31+
if (error instanceof NotFoundError) return 404
32+
if (error instanceof Provider.ModelNotFoundError) return 400
33+
if (error.name === "ProviderAuthValidationFailed") return 400
34+
if (error.name.startsWith("Worktree")) return 400
35+
return 500
36+
}),
37+
}),
38+
)
39+
}
40+
if (error instanceof Session.BusyError) {
41+
return Effect.succeed(
42+
HttpServerResponse.jsonUnsafe(new NamedError.Unknown({ message: error.message }).toObject(), {
43+
status: 400,
44+
}),
45+
)
46+
}
47+
48+
return Effect.succeed(
49+
HttpServerResponse.jsonUnsafe(
50+
new NamedError.Unknown({
51+
message: error instanceof Error && error.stack ? error.stack : String(error),
52+
}).toObject(),
53+
{ status: 500 },
54+
),
55+
)
56+
}),
57+
),
58+
).layer

packages/opencode/src/server/routes/instance/httpapi/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import { workspaceRouterMiddleware, workspaceRoutingLayer } from "./middleware/w
7373
import { disposeMiddleware } from "./lifecycle"
7474
import { memoMap } from "@opencode-ai/core/effect/memo-map"
7575
import * as ServerBackend from "@/server/backend"
76+
import { errorLayer } from "./middleware/error"
7677

7778
export const context = Context.makeUnsafe<unknown>(new Map())
7879

@@ -144,6 +145,7 @@ const uiRoute = HttpRouter.use((router) =>
144145
export function createRoutes(corsOptions?: CorsOptions) {
145146
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
146147
Layer.provide([
148+
errorLayer,
147149
cors(corsOptions),
148150
runtime,
149151
Account.defaultLayer,

0 commit comments

Comments
 (0)