effect(core): track stderr truncation; polish AppProcess callers (#27353)

This commit is contained in:
Kit Langton
2026-05-13 20:31:03 -04:00
committed by GitHub
parent ccb207f946
commit 42e6b7d541
5 changed files with 30 additions and 8 deletions
+4 -2
View File
@@ -31,7 +31,8 @@ export interface RunResult {
readonly exitCode: number readonly exitCode: number
readonly stdout: Buffer readonly stdout: Buffer
readonly stderr: Buffer readonly stderr: Buffer
readonly truncated: boolean readonly stdoutTruncated: boolean
readonly stderrTruncated: boolean
} }
export type Interface = ChildProcessSpawner["Service"] & { export type Interface = ChildProcessSpawner["Service"] & {
@@ -147,7 +148,8 @@ export const layer = Layer.effect(
exitCode, exitCode,
stdout: stdout.buffer, stdout: stdout.buffer,
stderr: stderr.buffer, stderr: stderr.buffer,
truncated: stdout.truncated, stdoutTruncated: stdout.truncated,
stderrTruncated: stderr.truncated,
} satisfies RunResult } satisfies RunResult
}), }),
) )
+21 -3
View File
@@ -20,7 +20,8 @@ describe("AppProcess", () => {
const result = yield* svc.run(cmd("-e", "process.stdout.write('hi\\n')")) const result = yield* svc.run(cmd("-e", "process.stdout.write('hi\\n')"))
expect(result.exitCode).toBe(0) expect(result.exitCode).toBe(0)
expect(result.stdout.toString("utf8")).toBe("hi\n") 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( it.effect(
"truncates output when maxOutputBytes is set", "truncates stdout when maxOutputBytes is set",
Effect.gen(function* () { Effect.gen(function* () {
const svc = yield* AppProcess.Service const svc = yield* AppProcess.Service
const result = yield* svc.run(cmd("-e", "process.stdout.write('0123456789')"), { maxOutputBytes: 5 }) const result = yield* svc.run(cmd("-e", "process.stdout.write('0123456789')"), { maxOutputBytes: 5 })
expect(result.exitCode).toBe(0) 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.length).toBe(5)
expect(result.stdout.toString("utf8")).toBe("01234") 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( it.effect(
"result includes command description", "result includes command description",
Effect.gen(function* () { Effect.gen(function* () {
+2 -1
View File
@@ -5,6 +5,7 @@ import { InstanceState } from "@/effect/instance-state"
import path from "path" import path from "path"
import { mergeDeep } from "remeda" import { mergeDeep } from "remeda"
import { Config } from "@/config/config" import { Config } from "@/config/config"
import { errorMessage } from "@/util/error"
import * as Log from "@opencode-ai/core/util/log" import * as Log from "@opencode-ai/core/util/log"
import * as Formatter from "./formatter" import * as Formatter from "./formatter"
@@ -100,7 +101,7 @@ export const layer = Layer.effect(
command: cmd, command: cmd,
...item.environment, ...item.environment,
file: filepath, file: filepath,
cause: error.message, cause: errorMessage(error.cause ?? error),
}) })
return undefined return undefined
}), }),
+1 -1
View File
@@ -124,7 +124,7 @@ export const layer = Layer.effect(
text: () => result.stdout.toString("utf8"), text: () => result.stdout.toString("utf8"),
stdout: result.stdout, stdout: result.stdout,
stderr: result.stderr, stderr: result.stderr,
truncated: result.truncated, truncated: result.stdoutTruncated || result.stderrTruncated,
} satisfies Result } satisfies Result
}, },
Effect.catch((err) => Effect.succeed(fail(err))), Effect.catch((err) => Effect.succeed(fail(err))),
+2 -1
View File
@@ -1,6 +1,7 @@
import { Effect, Layer, Schema, Context, Stream } from "effect" import { Effect, Layer, Schema, Context, Stream } from "effect"
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
import { withTransientReadRetry } from "@/util/effect-http-client" import { withTransientReadRetry } from "@/util/effect-http-client"
import { errorMessage } from "@/util/error"
import { ChildProcess } from "effect/unstable/process" import { ChildProcess } from "effect/unstable/process"
import { AppProcess } from "@opencode-ai/core/process" import { AppProcess } from "@opencode-ai/core/process"
import path from "path" import path from "path"
@@ -124,7 +125,7 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProce
stderr: result.stderr.toString("utf8"), stderr: result.stderr.toString("utf8"),
} }
}, },
Effect.catch(() => Effect.succeed({ code: 1, stdout: "", stderr: "" })), Effect.catch((err) => Effect.succeed({ code: 1, stdout: "", stderr: errorMessage(err) })),
) )
const getBrewFormula = Effect.fnUntraced(function* () { const getBrewFormula = Effect.fnUntraced(function* () {