From 69910f361ce4e5d1650c79d733d33e6d8f3a7b74 Mon Sep 17 00:00:00 2001 From: James Long Date: Wed, 27 May 2026 22:58:33 -0400 Subject: [PATCH] fix(server): use persisted session directory for existing-session routes (#29640) --- .../httpapi/middleware/workspace-routing.ts | 13 +++--- .../test/server/httpapi-session.test.ts | 42 ++++++++++++++++++- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts index 68ba95a7c4..66fe1e12e1 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts @@ -159,14 +159,14 @@ function planWorkspaceRequest( function planRequest( request: HttpServerRequest.HttpServerRequest, - sessionWorkspaceID?: WorkspaceID, + session?: Session.Info, ): Effect.Effect { return Effect.gen(function* () { const url = requestURL(request) const envWorkspaceID = configuredWorkspaceID() const workspaceID = url.pathname.startsWith("/api/") - ? selectedV2WorkspaceID(url, sessionWorkspaceID) - : selectedWorkspaceID(url, sessionWorkspaceID) + ? selectedV2WorkspaceID(url, session?.workspaceID) + : selectedWorkspaceID(url, session?.workspaceID) if (workspaceID === InvalidWorkspaceID) return RequestPlan.InvalidWorkspace() const workspace = yield* resolveWorkspace(workspaceID, envWorkspaceID) @@ -178,7 +178,10 @@ function planRequest( return yield* planWorkspaceRequest(request, url, workspace) } - return RequestPlan.Local({ directory: defaultDirectory(request, url), workspaceID: envWorkspaceID ?? workspaceID }) + return RequestPlan.Local({ + directory: session?.directory || defaultDirectory(request, url), + workspaceID: envWorkspaceID ?? workspaceID, + }) }) } @@ -226,7 +229,7 @@ function routeHttpApiWorkspace( Effect.catchDefect(() => Effect.succeed(undefined)), ) : undefined - const plan = yield* planRequest(request, session?.workspaceID) + const plan = yield* planRequest(request, session) return yield* routeWorkspace(client, effect, plan) }) } diff --git a/packages/opencode/test/server/httpapi-session.test.ts b/packages/opencode/test/server/httpapi-session.test.ts index 5c8f3fb24e..eb740125af 100644 --- a/packages/opencode/test/server/httpapi-session.test.ts +++ b/packages/opencode/test/server/httpapi-session.test.ts @@ -2,6 +2,7 @@ import { afterEach, describe, expect } from "bun:test" import { mkdir } from "node:fs/promises" import path from "node:path" import { Cause, Effect, Exit, Layer } from "effect" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Flag } from "@opencode-ai/core/flag/flag" import { registerAdapter } from "../../src/control-plane/adapters" import type { WorkspaceAdapter } from "../../src/control-plane/types" @@ -27,7 +28,9 @@ import * as DateTime from "effect/DateTime" import * as Log from "@opencode-ai/core/util/log" import { eq } from "drizzle-orm" import { resetDatabase } from "../fixture/db" -import { disposeAllInstances, TestInstance } from "../fixture/fixture" +import { disposeAllInstances, provideInstanceEffect, TestInstance, tmpdirScoped } from "../fixture/fixture" +import { TestLLMServer } from "../lib/llm-server" +import { testProviderConfig } from "../lib/test-provider" import { testEffect } from "../lib/effect" void Log.init({ print: false }) @@ -368,6 +371,43 @@ describe("session HttpApi", () => { { git: true, config: { formatter: false, lsp: false } }, ) + it.live("uses the persisted session directory for prompt requests", () => + Effect.gen(function* () { + const llm = yield* TestLLMServer + yield* llm.text("ok", { usage: { input: 1, output: 1 } }) + + const config = testProviderConfig(llm.url) + const sessionDirectory = yield* tmpdirScoped({ git: true, config }) + const requestDirectory = yield* tmpdirScoped({ git: true, config }) + const session = yield* createSession({ title: "directory regression" }).pipe(provideInstanceEffect(sessionDirectory)) + + const response = yield* request( + `${pathFor(SessionPaths.prompt, { sessionID: session.id })}?directory=${encodeURIComponent(requestDirectory)}`, + { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + agent: "build", + model: { providerID: "test", modelID: "test-model" }, + parts: [{ type: "text", text: "which directory?" }], + }), + }, + ) + + expect(response.status).toBe(200) + yield* responseJson(response) + + const messages = yield* Session.use + .messages({ sessionID: session.id }) + .pipe(provideInstanceEffect(sessionDirectory), Effect.orDie) + const assistant = messages.find((message) => message.info.role === "assistant") + expect(assistant?.info.role === "assistant" ? assistant.info.path : undefined).toEqual({ + cwd: sessionDirectory, + root: sessionDirectory, + }) + }).pipe(Effect.provide(TestLLMServer.layer), Effect.provide(CrossSpawnSpawner.defaultLayer)), + ) + it.instance( "returns v2 public request errors for cursor and workspace query failures", () =>