From 42e6b7d54147fd54aef59ad375a522c4245c1235 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 13 May 2026 20:31:03 -0400 Subject: [PATCH] effect(core): track stderr truncation; polish AppProcess callers (#27353) --- packages/core/src/process.ts | 6 ++++-- packages/core/test/process/process.test.ts | 24 ++++++++++++++++++--- packages/opencode/src/format/index.ts | 3 ++- packages/opencode/src/git/index.ts | 2 +- packages/opencode/src/installation/index.ts | 3 ++- 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/core/src/process.ts b/packages/core/src/process.ts index 76ea9cf3f0..f076ea4e42 100644 --- a/packages/core/src/process.ts +++ b/packages/core/src/process.ts @@ -31,7 +31,8 @@ export interface RunResult { readonly exitCode: number readonly stdout: Buffer readonly stderr: Buffer - readonly truncated: boolean + readonly stdoutTruncated: boolean + readonly stderrTruncated: boolean } export type Interface = ChildProcessSpawner["Service"] & { @@ -147,7 +148,8 @@ export const layer = Layer.effect( exitCode, stdout: stdout.buffer, stderr: stderr.buffer, - truncated: stdout.truncated, + stdoutTruncated: stdout.truncated, + stderrTruncated: stderr.truncated, } satisfies RunResult }), ) diff --git a/packages/core/test/process/process.test.ts b/packages/core/test/process/process.test.ts index 5cc73e6169..49409ff9dd 100644 --- a/packages/core/test/process/process.test.ts +++ b/packages/core/test/process/process.test.ts @@ -20,7 +20,8 @@ describe("AppProcess", () => { const result = yield* svc.run(cmd("-e", "process.stdout.write('hi\\n')")) expect(result.exitCode).toBe(0) expect(result.stdout.toString("utf8")).toBe("hi\n") - expect(result.truncated).toBe(false) + expect(result.stdoutTruncated).toBe(false) + expect(result.stderrTruncated).toBe(false) }), ) @@ -84,17 +85,34 @@ describe("AppProcess", () => { ) it.effect( - "truncates output when maxOutputBytes is set", + "truncates stdout when maxOutputBytes is set", Effect.gen(function* () { const svc = yield* AppProcess.Service const result = yield* svc.run(cmd("-e", "process.stdout.write('0123456789')"), { maxOutputBytes: 5 }) expect(result.exitCode).toBe(0) - expect(result.truncated).toBe(true) + expect(result.stdoutTruncated).toBe(true) + expect(result.stderrTruncated).toBe(false) expect(result.stdout.length).toBe(5) expect(result.stdout.toString("utf8")).toBe("01234") }), ) + it.effect( + "truncates stderr when maxErrorBytes is set", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const result = yield* svc.run( + cmd("-e", "process.stderr.write('0123456789')"), + { maxErrorBytes: 5 }, + ) + expect(result.exitCode).toBe(0) + expect(result.stdoutTruncated).toBe(false) + expect(result.stderrTruncated).toBe(true) + expect(result.stderr.length).toBe(5) + expect(result.stderr.toString("utf8")).toBe("01234") + }), + ) + it.effect( "result includes command description", Effect.gen(function* () { diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index d8365e0fa7..fa22014439 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -5,6 +5,7 @@ import { InstanceState } from "@/effect/instance-state" import path from "path" import { mergeDeep } from "remeda" import { Config } from "@/config/config" +import { errorMessage } from "@/util/error" import * as Log from "@opencode-ai/core/util/log" import * as Formatter from "./formatter" @@ -100,7 +101,7 @@ export const layer = Layer.effect( command: cmd, ...item.environment, file: filepath, - cause: error.message, + cause: errorMessage(error.cause ?? error), }) return undefined }), diff --git a/packages/opencode/src/git/index.ts b/packages/opencode/src/git/index.ts index 4c88edaea3..5e76b7f731 100644 --- a/packages/opencode/src/git/index.ts +++ b/packages/opencode/src/git/index.ts @@ -124,7 +124,7 @@ export const layer = Layer.effect( text: () => result.stdout.toString("utf8"), stdout: result.stdout, stderr: result.stderr, - truncated: result.truncated, + truncated: result.stdoutTruncated || result.stderrTruncated, } satisfies Result }, Effect.catch((err) => Effect.succeed(fail(err))), diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 00fbabe351..9b0e06c4af 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -1,6 +1,7 @@ import { Effect, Layer, Schema, Context, Stream } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { withTransientReadRetry } from "@/util/effect-http-client" +import { errorMessage } from "@/util/error" import { ChildProcess } from "effect/unstable/process" import { AppProcess } from "@opencode-ai/core/process" import path from "path" @@ -124,7 +125,7 @@ export const layer: Layer.Layer Effect.succeed({ code: 1, stdout: "", stderr: "" })), + Effect.catch((err) => Effect.succeed({ code: 1, stdout: "", stderr: errorMessage(err) })), ) const getBrewFormula = Effect.fnUntraced(function* () {