Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions packages/opencode/src/peer/claude/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 4 additions & 2 deletions packages/opencode/src/peer/claude/sidecar-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof Process.spawn>
}
Expand All @@ -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
Expand Down
Loading