diff --git a/packages/cli/src/node/index.ts b/packages/cli/src/node/index.ts index b7bb7db5414..3559a614ca1 100644 --- a/packages/cli/src/node/index.ts +++ b/packages/cli/src/node/index.ts @@ -1,3 +1,9 @@ import "./plugin-runtime.promise" import "./plugin-runtime.effect" -import "../index" + +process.stdout.on("error", (error) => { + if ("code" in error && error.code === "EPIPE") return + throw error +}) + +await import("../index") diff --git a/packages/cli/src/services/standalone.ts b/packages/cli/src/services/standalone.ts index 273cccbf495..d1ea7fc9c57 100644 --- a/packages/cli/src/services/standalone.ts +++ b/packages/cli/src/services/standalone.ts @@ -1,7 +1,7 @@ import { Service, type Endpoint } from "@opencode-ai/client/effect/service" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" -import { Effect, Schema, Stream } from "effect" +import { Deferred, Effect, Schema, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { randomBytes } from "node:crypto" import { selfCommand } from "../util/process" @@ -36,8 +36,16 @@ const makeEndpoint = Effect.fn("cli.standalone.endpoint")( const password = randomBytes(32).toString("base64url") const spawner = yield* ChildProcessSpawner.ChildProcessSpawner const proc = yield* spawner.spawn(command(password, options)) - const output = yield* proc.stdout.pipe(Stream.decodeText(), Stream.splitLines, Stream.take(1), Stream.mkString) - if (!output) return yield* Effect.fail(new Error("Standalone server exited before reporting readiness")) + const readyLine = yield* Deferred.make() + // Keep draining stdout after readiness so later server writes cannot hit EPIPE. + yield* proc.stdout.pipe( + Stream.decodeText(), + Stream.splitLines, + Stream.runForEach((line) => Deferred.succeed(readyLine, line)), + Effect.ensuring(Deferred.fail(readyLine, new Error("Standalone server exited before reporting readiness"))), + Effect.forkScoped, + ) + const output = yield* Deferred.await(readyLine) const ready = yield* Effect.tryPromise(() => decodeReady(output)) return { url: ready.url,