Skip to content

Commit 5b5228f

Browse files
committed
perf(run-events): unblock bus dispatch with runFork in subscriber callbacks
Bus.subscribeCallback wraps the callback's return value in Effect.tryPromise(() => Promise.resolve().then(() => callback(msg))) inside a runForEach loop, so a Promise-returning callback (such as Effect.runPromise) serializes handler completion per subscription — each descendant question/permission event waits for the previous handler's reject/reply round trip before dispatch advances. Replace Effect.runPromise with Effect.runFork. runFork returns a Fiber synchronously (non-thenable), so the bus's tryPromise resolves immediately and the next event dispatches without waiting. Handler defects no longer surface through Bus.on's tryPromise.catch, so wrap the forked effect with Effect.tapCause + log.error. Drops the per-event Promise wrapper allocation and unblocks concurrent dispatch for long-running subagent loops with many simultaneous descendants. Addresses audit finding F7 (Opus diamond review, 2026-04-22).
1 parent 379dbf4 commit 5b5228f

1 file changed

Lines changed: 15 additions & 2 deletions

File tree

packages/opencode/src/cli/cmd/run-events.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,21 @@ export const make = Effect.fn("RunEvents.make")(function* (config: Config) {
8686
}
8787
}
8888

89+
// Bus.subscribeCallback (src/bus/index.ts:126-152) wraps the callback return
90+
// in `Effect.tryPromise(() => Promise.resolve().then(() => callback(msg)))`
91+
// inside `runForEach`, so a Promise-returning callback (like Effect.runPromise)
92+
// serializes handler completion per subscription. runFork returns a Fiber
93+
// synchronously (non-thenable), unblocking dispatch so descendant question/
94+
// permission events are processed concurrently — important for long-running
95+
// subagent loops with many simultaneous descendants. Defects inside the fiber
96+
// no longer surface through Bus.on's tryPromise.catch, so log them here.
97+
const fork = (effect: Effect.Effect<void>) =>
98+
Effect.runFork(
99+
effect.pipe(Effect.tapCause((cause) => Effect.sync(() => log.error("handler failed", { cause })))),
100+
)
101+
89102
const unsubQuestion = yield* bus.subscribeCallback(Question.Event.Asked, (evt) =>
90-
Effect.runPromise(
103+
fork(
91104
Effect.gen(function* () {
92105
const mine = yield* isDescendant(evt.properties.sessionID)
93106
if (!mine) return
@@ -98,7 +111,7 @@ export const make = Effect.fn("RunEvents.make")(function* (config: Config) {
98111
)
99112

100113
const unsubPermission = yield* bus.subscribeCallback(Permission.Event.Asked, (evt) =>
101-
Effect.runPromise(
114+
fork(
102115
Effect.gen(function* () {
103116
const mine = yield* isDescendant(evt.properties.sessionID)
104117
if (!mine) return

0 commit comments

Comments
 (0)