From dac33c8ecb2833287f69567b65c9c3dff486f6eb Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 1 Jun 2026 11:49:54 +0200 Subject: [PATCH] fix(e2e): cap pty transcript output --- scripts/e2e/lib/run-with-pty.mjs | 32 +++++++++++++++++-- test/scripts/e2e-run-with-pty.test.ts | 46 ++++++++++++++++++++------- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/scripts/e2e/lib/run-with-pty.mjs b/scripts/e2e/lib/run-with-pty.mjs index c3e90c1d2ca..dd63de45a39 100644 --- a/scripts/e2e/lib/run-with-pty.mjs +++ b/scripts/e2e/lib/run-with-pty.mjs @@ -5,6 +5,7 @@ import { spawn } from "@lydell/node-pty"; import { readPositiveIntEnv } from "./env-limits.mjs"; const [logPath, command, ...args] = process.argv.slice(2); +const OUTPUT_MAX_BYTES = readPositiveIntEnv("OPENCLAW_E2E_PTY_OUTPUT_MAX_BYTES", 16 * 1024 * 1024); if (!logPath || !command) { console.error("usage: run-with-pty.mjs [args...]"); @@ -21,10 +22,37 @@ const pty = spawn(command, args, { }); let exiting = false; +const outputLimitMarker = `\n[run-with-pty output truncated after ${OUTPUT_MAX_BYTES} bytes]\n`; +const outputState = { + bytes: 0, + truncated: false, +}; + +function writeCappedOutput(data) { + if (outputState.truncated) { + return; + } + const buffer = Buffer.from(data); + const remainingBytes = OUTPUT_MAX_BYTES - outputState.bytes; + if (buffer.byteLength <= remainingBytes) { + outputState.bytes += buffer.byteLength; + log.write(buffer); + process.stdout.write(buffer); + return; + } + if (remainingBytes > 0) { + const head = buffer.subarray(0, remainingBytes); + log.write(head); + process.stdout.write(head); + } + outputState.bytes = OUTPUT_MAX_BYTES; + outputState.truncated = true; + log.write(outputLimitMarker); + process.stdout.write(outputLimitMarker); +} pty.onData((data) => { - log.write(data); - process.stdout.write(data); + writeCappedOutput(data); }); pty.onExit(({ exitCode, signal }) => { diff --git a/test/scripts/e2e-run-with-pty.test.ts b/test/scripts/e2e-run-with-pty.test.ts index f54b610f791..822a35856db 100644 --- a/test/scripts/e2e-run-with-pty.test.ts +++ b/test/scripts/e2e-run-with-pty.test.ts @@ -11,19 +11,18 @@ const scriptPath = path.join(repoRoot, "scripts/e2e/lib/run-with-pty.mjs"); function runPtyProbe( logPath: string, env: Record = {}, + command: string[] = [ + "/bin/bash", + "-lc", + 'printf "prompt\\n"; IFS= read -r value; printf "got:%s\\n" "$value"', + ], + input = "abc\n", ): Promise<{ code: number | null; stdout: string; stderr: string }> { return new Promise((resolve, reject) => { - const child = spawn( - process.execPath, - [ - scriptPath, - logPath, - "/bin/bash", - "-lc", - 'printf "prompt\\n"; IFS= read -r value; printf "got:%s\\n" "$value"', - ], - { env: { ...process.env, ...env }, stdio: ["pipe", "pipe", "pipe"] }, - ); + const child = spawn(process.execPath, [scriptPath, logPath, ...command], { + env: { ...process.env, ...env }, + stdio: ["pipe", "pipe", "pipe"], + }); let stdout = ""; let stderr = ""; const timeout = setTimeout(() => { @@ -47,7 +46,7 @@ function runPtyProbe( clearTimeout(timeout); resolve({ code, stdout, stderr }); }); - child.stdin.end("abc\n"); + child.stdin.end(input); }); } @@ -81,4 +80,27 @@ describe("run-with-pty", () => { await rm(tempRoot, { recursive: true, force: true }); } }); + + it("caps noisy PTY output in stdout and transcript logs", async () => { + const tempRoot = await mkdtemp(path.join(os.tmpdir(), "openclaw-run-with-pty-")); + const logPath = path.join(tempRoot, "pty.log"); + try { + const result = await runPtyProbe( + logPath, + { OPENCLAW_E2E_PTY_OUTPUT_MAX_BYTES: "64" }, + [process.execPath, "-e", "process.stdout.write('x'.repeat(2048))"], + "", + ); + const log = await readFile(logPath, "utf8"); + const marker = "[run-with-pty output truncated after 64 bytes]"; + + expect(result).toMatchObject({ code: 0, stderr: "" }); + expect(result.stdout).toContain(marker); + expect(log).toContain(marker); + expect(result.stdout.length).toBeLessThan(512); + expect(log.length).toBeLessThan(512); + } finally { + await rm(tempRoot, { recursive: true, force: true }); + } + }); });