From fafacc09f5c5b841a1d1e8bb7462f224f135a707 Mon Sep 17 00:00:00 2001 From: engineer Date: Tue, 11 Aug 2026 19:31:22 -0700 Subject: [PATCH] fix(server): wire the real SessionExecution local layer, not the noop stub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HTTP server's handler layer graph provided SessionV2.defaultLayer, which internally bakes in SessionExecution.noopLayer as part of its own Layer.provide chain (packages/core/src/session.ts). That closes over the session's SessionExecution.Service dependency permanently at the point defaultLayer itself is constructed. The sibling `Layer.provide(SessionExecutionLocal.defaultLayer)` in handlers.ts could never reach past that already-resolved dependency, since Effect's Layer.provide binds a layer's requirement when the layer is built, not when it's later merged into an unrelated layer graph. Practical symptom: POST /session/{id}/prompt admitted durably (200, admittedSeq set, a durable row written), but session.wake()/resume() silently no-op'd, so the agent loop never actually ran. Sessions stayed at cost=0, tokens=0, with no assistant message ever produced, and POST /session/{id}/wait 503'd with "Session wait is not available yet" (a separate, still-unimplemented V2 API stub, not the primary blocker). Fix: compose the session layer here from the raw SessionV2.layer plus its concrete sub-layers, substituting SessionExecutionLocal.defaultLayer for the noop. This mirrors the already-correct pattern used by packages/core/src/public/opencode.ts's SessionsLayer, which never had this bug because it wires SessionV2.layer (not defaultLayer) with the local executor directly. Not run through bun install/typecheck locally — both the dev host that surfaced this bug and this workstation are disk-constrained (<2GB free). Relying on CI (github-hosted runners) to validate the build. --- packages/server/src/handlers.ts | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/server/src/handlers.ts b/packages/server/src/handlers.ts index d26c1618f0d6..1f6df48976be 100644 --- a/packages/server/src/handlers.ts +++ b/packages/server/src/handlers.ts @@ -2,6 +2,11 @@ import { SessionV2 } from "@opencode-ai/core/session" import { LocationServiceMap } from "@opencode-ai/core/location-layer" import { PermissionSaved } from "@opencode-ai/core/permission/saved" import { PtyTicket } from "@opencode-ai/core/pty/ticket" +import { SessionStore } from "@opencode-ai/core/session/store" +import { SessionProjector } from "@opencode-ai/core/session/projector" +import { EventV2 } from "@opencode-ai/core/event" +import { Database } from "@opencode-ai/core/database/database" +import { ProjectV2 } from "@opencode-ai/core/project" import { Layer } from "effect" import { layer as locationLayer } from "./groups/location" import { sessionLocationLayer } from "./middleware/session-location" @@ -48,8 +53,32 @@ export const handlers = Layer.mergeAll( ).pipe( Layer.provide(sessionLocationLayer), Layer.provide(locationLayer), - Layer.provide(SessionV2.defaultLayer), - Layer.provide(SessionExecutionLocal.defaultLayer), + // NOTE: intentionally NOT `SessionV2.defaultLayer`. That export bakes + // `SessionExecution.noopLayer` into its own dependency graph (see + // packages/core/src/session.ts), which permanently resolves the + // session's SessionExecution.Service requirement to a no-op before this + // layer is ever built. Providing `SessionExecutionLocal.defaultLayer` + // from out here (as this file used to, right below) cannot reach past + // that already-closed-over dependency: Effect's Layer.provide binds a + // layer's requirement at the point the layer itself is constructed, not + // at the point it's later merged in. The practical symptom was that + // POST /session/{id}/prompt admitted durably (200, admittedSeq set) but + // session.wake()/resume() were silently no-ops, so the agent loop never + // ran (cost/tokens stayed 0, no assistant message was ever produced). + // Compose the session layer here instead, swapping in the real local + // executor, mirroring the (correct) pattern already used by + // packages/core/src/public/opencode.ts's `SessionsLayer`. + Layer.provide( + SessionV2.layer.pipe( + Layer.provide(SessionExecutionLocal.defaultLayer), + Layer.provide(SessionStore.defaultLayer), + Layer.provide(SessionProjector.defaultLayer), + Layer.provide(EventV2.defaultLayer), + Layer.provide(Database.defaultLayer), + Layer.provide(ProjectV2.defaultLayer), + Layer.orDie, + ), + ), Layer.provide(PermissionSaved.defaultLayer), Layer.provide(PtyTicket.defaultLayer), Layer.provide(LocationServiceMap.layer),