From dd680c10c8ef898143d15e4830c7d0e19a49eb69 Mon Sep 17 00:00:00 2001 From: James Murdza Date: Sun, 2 Aug 2026 13:07:59 +0700 Subject: [PATCH] fix(server): log upstream 5xx bodies from proxied workspace requests A 5xx from a remote workspace sandbox previously passed through the proxy opaquely: the detailed cause was logged only inside the sandbox, so the host tailing its own log saw nothing. Buffer the (small) error body for 5xx responses, log it on the host, and forward it unchanged with its original content-type so the client can still read the structured error ref. Co-Authored-By: Claude Opus 4.8 --- .../instance/httpapi/middleware/proxy.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/proxy.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/proxy.ts index e5362f8cbe13..d21ab5647c1e 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/proxy.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/proxy.ts @@ -97,6 +97,29 @@ export function http( headers.delete("content-encoding") headers.delete("content-length") + // An upstream 5xx from a remote workspace sandbox arrives here as an opaque + // status — its real cause (and log line) live only inside the sandbox. Buffer + // the small error body, log it locally so it shows up in the host's log, and + // forward it unchanged (preserving content-type so the client can still parse + // the structured error, e.g. its `ref`). + if (response.status >= 500) { + const body = yield* response.text.pipe(Effect.catch(() => Effect.succeed(""))) + const contentType = response.headers["content-type"] ?? "application/json" + headers.delete("content-type") + yield* Effect.logError("workspace proxy upstream error", { + url: url.toString(), + method: request.method, + status: response.status, + body: body.slice(0, 2000), + }) + return HttpServerResponse.text(body, { + status: response.status, + statusText: statusText(response), + headers, + contentType, + }) + } + return HttpServerResponse.stream(response.stream.pipe(Stream.catchCause(() => Stream.empty)), { status: response.status, statusText: statusText(response),