diff --git a/packages/opencode/src/server/shared/workspace-routing.ts b/packages/opencode/src/server/shared/workspace-routing.ts index 366c455dd6bb..c57e392c2191 100644 --- a/packages/opencode/src/server/shared/workspace-routing.ts +++ b/packages/opencode/src/server/shared/workspace-routing.ts @@ -32,5 +32,14 @@ export function workspaceProxyURL(target: string | URL, requestURL: URL) { proxyURL.search = requestURL.search proxyURL.hash = requestURL.hash proxyURL.searchParams.delete("workspace") + // The SDK rewrites the client's `x-opencode-directory` header into a + // `?directory=` query before sending. When the request is then proxied + // to a remote workspace, that local-machine path leaks through and the + // remote falls back to `worktree="/"` because the path doesn't exist on + // its filesystem — which corrupts downstream `path.relative()` callers + // (notably `sessionListQuery`) and wipes the TUI's session list. + // Strip it so the workspace adapter's `target.headers["x-opencode-directory"]` + // (the workspace's directory on the remote) wins. + proxyURL.searchParams.delete("directory") return proxyURL } diff --git a/packages/opencode/test/server/workspace-routing.test.ts b/packages/opencode/test/server/workspace-routing.test.ts index a921ae2774c5..f851fc00087e 100644 --- a/packages/opencode/test/server/workspace-routing.test.ts +++ b/packages/opencode/test/server/workspace-routing.test.ts @@ -75,6 +75,18 @@ describe("workspaceProxyURL", () => { expect(result.searchParams.get("keep")).toBe("yes") }) + test("strips directory query so the remote uses its own working directory", () => { + // The SDK rewrites the client's `x-opencode-directory` header into a + // `?directory=` query before sending. Forwarding that local-machine + // path to a remote workspace makes the remote fall back to + // `worktree="/"` (the path doesn't exist on its filesystem) and + // corrupts downstream `path.relative()` callers. + const url = new URL("http://localhost/path?workspace=ws_123&directory=%2FUsers%2Falice%2Fproj") + const result = workspaceProxyURL("http://remote:8080", url) + expect(result.searchParams.get("workspace")).toBeNull() + expect(result.searchParams.get("directory")).toBeNull() + }) + test("preserves hash from request", () => { const url = new URL("http://localhost/page#section") const result = workspaceProxyURL("http://remote:8080", url)