diff --git a/src/sandbox/process-output.ts b/src/sandbox/process-output.ts index 98508bf..c5e7ea6 100644 --- a/src/sandbox/process-output.ts +++ b/src/sandbox/process-output.ts @@ -66,12 +66,16 @@ export class ProcessOutput { readonly maxBytes: number ) {} - failure(message: string, code = "incomplete_output"): HyperbrowserError { + failure( + message: string, + code = "incomplete_output", + details: Record = {} + ): HyperbrowserError { return new HyperbrowserError(message, { code, service: "runtime", retryable: false, - details: { process_id: this.processId, last_seq: this.seq }, + details: { process_id: this.processId, last_seq: this.seq, ...details }, }); } @@ -120,8 +124,9 @@ export class ProcessOutput { this.size += raw.length; if (this.size > this.maxBytes) { throw this.failure( - "Command output exceeds maxOutputBytes; increase the collection limit or disconnect a detached process", - "output_limit_exceeded" + `Command output exceeds maxOutputBytes (${this.size} bytes received, limit ${this.maxBytes}); increase the collection limit or disconnect a detached process`, + "output_limit_exceeded", + { max_output_bytes: this.maxBytes, received_bytes: this.size } ); } this.seq = data.seq; diff --git a/tests/unit/process-collection.test.ts b/tests/unit/process-collection.test.ts index 276a963..b6af709 100644 --- a/tests/unit/process-collection.test.ts +++ b/tests/unit/process-collection.test.ts @@ -147,6 +147,17 @@ describe("sandbox process output collection", () => { expect(transport.calls).toHaveLength(1); }); + test("output limit error reports the limit and bytes received", async () => { + const transport = new FakeTransport([output(1, Buffer.from("too much")), done(1)] as RuntimeSSEEvent[]); + const error = await new SandboxProcessesApi(transport.asTransport()) + .exec("test", { maxOutputBytes: 4 }) + .then(() => null, (e: unknown) => e); + const failure = error as HyperbrowserError; + expect(failure.code).toBe("output_limit_exceeded"); + expect(failure.details).toMatchObject({ max_output_bytes: 4, received_bytes: 8 }); + expect(failure.message).toContain("8 bytes received, limit 4"); + }); + test("wait timeout keeps the collector alive", async () => { const transport = new FakeTransport([output(1, Buffer.from("later")), done(1)]); transport.gate = new Gate();