Compare commits

..

2 Commits

Author SHA1 Message Date
James Murdza f0afb6750e fix(server): log upstream 5xx bodies from proxied workspace requests (#40135)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 09:43:59 -04:00
James Murdza 703d09f306 fix(server): don't forward host directory to remote workspace (#40136)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 09:43:45 -04:00
5 changed files with 46 additions and 45 deletions
@@ -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),
@@ -34,5 +34,12 @@ export function workspaceProxyURL(target: string | URL, requestURL: URL) {
proxyURL.search = requestURL.search
proxyURL.hash = requestURL.hash
proxyURL.searchParams.delete("workspace")
// The `directory` param is the *host's* working directory (e.g. a Windows
// path like `F:\proj`). It is meaningless — and dangerous — on the remote:
// the sandbox would `path.resolve` it against its own cwd, producing a bogus
// path like `/home/daytona/workspace/repo/F:\proj` that does not exist and
// crashes prompt handling. Drop it so the remote falls back to its own
// project root. This mirrors ProxyUtil.headers stripping `x-opencode-directory`.
proxyURL.searchParams.delete("directory")
return proxyURL
}
@@ -80,6 +80,13 @@ describe("workspaceProxyURL", () => {
expect(result.searchParams.get("keep")).toBe("yes")
})
test("strips the host directory param so the remote resolves its own root", () => {
const url = new URL("http://localhost/session/abc?directory=F%3A%5Cproj&keep=yes")
const result = workspaceProxyURL("http://remote:8080/base", url)
expect(result.searchParams.get("directory")).toBeNull()
expect(result.searchParams.get("keep")).toBe("yes")
})
test("preserves hash from request", () => {
const url = new URL("http://localhost/page#section")
const result = workspaceProxyURL("http://remote:8080", url)
+9 -34
View File
@@ -1,13 +1,10 @@
import { RGBA, TextAttributes } from "@opentui/core"
import { For, type JSX } from "solid-js"
import { useTerminalDimensions } from "@opentui/solid"
import { tint, useTheme } from "../context/theme"
import { go, logo } from "../logo"
import { logo } from "../logo"
export function Logo() {
const { theme } = useTheme()
const dimensions = useTerminalDimensions()
const variant = () => logoVariant(dimensions().width, dimensions().height)
const renderLine = (line: string, fg: RGBA, bold: boolean): JSX.Element[] => {
const shadow = tint(theme.background, fg, 0.25)
@@ -51,36 +48,14 @@ export function Logo() {
return (
<box>
{variant() === "hidden" ? null : variant() === "compact" ? (
<For each={go.right.slice(1)}>
{(line) => <box flexDirection="row">{renderLine(line, theme.text, true)}</box>}
</For>
) : variant() === "stacked" ? (
<>
<For each={logo.left.slice(1)}>
{(line) => <box flexDirection="row">{renderLine(line, theme.textMuted, false)}</box>}
</For>
<For each={logo.right}>
{(line) => <box flexDirection="row">{renderLine(line, theme.text, true)}</box>}
</For>
</>
) : (
<For each={logo.left}>
{(line, index) => (
<box flexDirection="row" gap={1}>
<box flexDirection="row">{renderLine(line, theme.textMuted, false)}</box>
<box flexDirection="row">{renderLine(logo.right[index()], theme.text, true)}</box>
</box>
)}
</For>
)}
<For each={logo.left}>
{(line, index) => (
<box flexDirection="row" gap={1}>
<box flexDirection="row">{renderLine(line, theme.textMuted, false)}</box>
<box flexDirection="row">{renderLine(logo.right[index()], theme.text, true)}</box>
</box>
)}
</For>
</box>
)
}
export function logoVariant(width: number, height: number) {
if (height < 12) return "hidden"
if (width < 22) return "compact"
if (width < 44) return "stacked"
return "full"
}
-11
View File
@@ -1,11 +0,0 @@
import { expect, test } from "bun:test"
import { logoVariant } from "../src/component/logo"
test("adapts the logo to constrained terminals", () => {
expect(logoVariant(19, 24)).toBe("compact")
expect(logoVariant(21, 24)).toBe("compact")
expect(logoVariant(22, 24)).toBe("stacked")
expect(logoVariant(43, 24)).toBe("stacked")
expect(logoVariant(44, 24)).toBe("full")
expect(logoVariant(80, 11)).toBe("hidden")
})