From 3c9867ad7934d64f3f83b127bff21da297ee896d Mon Sep 17 00:00:00 2001 From: William Hardesty Date: Sun, 24 May 2026 13:28:55 -0400 Subject: [PATCH] fix: await event loop in non-interactive `opencode run` In non-interactive mode, the event processing loop was started with fire-and-forget (Promise.catch but not awaited). When the prompt() call returned its HTTP acknowledgment, execute() returned immediately, and the Effect framework's cleanup disposed the in-process server, killing the SSE stream before the model finished generating. Only the step_start event was emitted (it arrives before prompt() returns). All text, tool_use, and step_finish events were lost because the stream was torn down mid-generation. Fix: store the loop promise and await it after the prompt/command call completes. This keeps the process alive until the session reaches idle and all events have been processed. Tested: `opencode run 'what is 2+2?' --format json` now correctly emits step_start + text + step_finish events (previously only step_start). --- packages/opencode/src/cli/cmd/run.ts | 34 ++++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index b80a2389ef24..1b99cb5d3e54 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -767,10 +767,7 @@ export const RunCommand = effectCmd({ if (!args.interactive) { const events = await client.event.subscribe() - loop(client, events).catch((e) => { - console.error(e) - process.exit(1) - }) + const loopDone = loop(client, events) if (args.command) { const result = await client.session.command({ @@ -785,19 +782,26 @@ export const RunCommand = effectCmd({ if (!emit("error", { error: result.error })) UI.error(formatRunError(result.error)) process.exitCode = 1 } - return + } else { + const model = pick(args.model) + const result = await client.session.prompt({ + sessionID, + agent, + model, + variant: args.variant, + parts: [...files, { type: "text", text: message }], + }) + if (result.error) { + if (!emit("error", { error: result.error })) UI.error(formatRunError(result.error)) + process.exitCode = 1 + } } - const model = pick(args.model) - const result = await client.session.prompt({ - sessionID, - agent, - model, - variant: args.variant, - parts: [...files, { type: "text", text: message }], - }) - if (result.error) { - if (!emit("error", { error: result.error })) UI.error(formatRunError(result.error)) + try { + const loopError = await loopDone + if (loopError) process.exitCode = 1 + } catch (e) { + console.error(e) process.exitCode = 1 } return