From 232f95051cf45d8bdb72ce33a105753126094987 Mon Sep 17 00:00:00 2001 From: AndreasS Date: Mon, 21 Sep 2026 00:12:06 +0200 Subject: [PATCH] fix(peer): log dropped sidecar inbound, correct InstanceRef docs --- .../opencode/src/peer/claude/lifecycle.ts | 36 ++++++++++++++----- .../src/peer/claude/sidecar-manager.ts | 6 ++-- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/peer/claude/lifecycle.ts b/packages/opencode/src/peer/claude/lifecycle.ts index f9f0cf62df66..21099405a64e 100644 --- a/packages/opencode/src/peer/claude/lifecycle.ts +++ b/packages/opencode/src/peer/claude/lifecycle.ts @@ -80,21 +80,38 @@ const layer = Layer.effect( // This runs from a background event listener, not inside any request's // own instance context (unlike a tool call, which inherits one from // whatever originally invoked the session) — an `InstanceRef` has to be - // established explicitly before any session-scoped service will work, - // `Session.Service.get` included. The directory therefore cannot come - // from `session.get()` — that call itself dies with "InstanceRef not - // provided" without one already in scope. `ensureSidecar` was given the - // directory at registration time and the sidecar manager still has it; - // read it from there instead. + // established explicitly before any session-scoped service will work + // (`SessionStatus.get` and `SessionPrompt.prompt` need one; + // `Session.get` itself reads the global DB). The directory therefore + // cannot come from a session-scoped lookup — `ensureSidecar` was given + // the directory at registration time and the sidecar manager still has + // it; read it from there instead. `inbound.sessionID` is the sidecar's + // own owner ID, not sender-controlled text, so resolving/booting the + // instance before the `info` existence check below cannot be triggered + // by a remote peer. const directory = sidecarDirectoryFor(inbound.sessionID) - if (!directory) return + if (!directory) { + runFork( + Effect.logWarning("claude sidecar: dropping inbound for unmanaged session", { + "session.id": inbound.sessionID, + msgID: inbound.msgID, + }), + ) + return + } runFork( instanceStore .provide( { directory }, Effect.gen(function* () { const info = yield* session.get(SessionID.make(inbound.sessionID)).pipe(Effect.orElseSucceed(() => undefined)) - if (!info) return + if (!info) { + yield* Effect.logWarning("claude sidecar: dropping inbound for unknown session", { + "session.id": inbound.sessionID, + msgID: inbound.msgID, + }) + return + } // `fromName` and `from` are envelope attributes — display and // addressing only, never authorization (codec.ts). `from` is the // sender's return address; it is what the receiving agent has to @@ -221,6 +238,9 @@ const layer = Layer.effect( // that is doing something is exactly the one worth an address, so // register it the first time it reports a status. if (isManaged(data.sessionID)) return Effect.void + // Safe without an explicit `InstanceRef`: `Session.get` reads the + // global DB directly (no `InstanceState.context`), and `ensureFor` + // only spawns a sidecar — no session-scoped service is touched. return session.get(SessionID.make(data.sessionID)).pipe( Effect.flatMap((info) => Effect.sync(() => ensureFor(info))), Effect.ignore, diff --git a/packages/opencode/src/peer/claude/sidecar-manager.ts b/packages/opencode/src/peer/claude/sidecar-manager.ts index 18f3b8bb473e..bf5b5afe9521 100644 --- a/packages/opencode/src/peer/claude/sidecar-manager.ts +++ b/packages/opencode/src/peer/claude/sidecar-manager.ts @@ -28,6 +28,7 @@ export interface SidecarInfo { interface Managed extends SidecarInfo { name: string + // Immutable: the owning session's project directory never changes. directory: string child: ReturnType } @@ -44,8 +45,9 @@ export function sidecarNameFor(sessionID: string): string | undefined { /** * The owning session's project directory, known since `ensureSidecar` was * called for it. `deliver` needs this to resolve an `InstanceRef` *before* - * calling any session-scoped service (`Session.Service.get` itself requires - * one) — looking the directory up via the session store would be circular. + * calling any session-scoped service (`SessionStatus.get` and + * `SessionPrompt.prompt` need one) — looking the directory up via a + * session-scoped store would be circular. */ export function sidecarDirectoryFor(sessionID: string): string | undefined { return active.get(sessionID)?.directory