From 20db67b89b3e2054ab365fe44fbe5dfc5c4164a5 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 21 Mar 2026 20:37:13 +0100 Subject: [PATCH] refactor: limit stderr length to 50 lines --- app/server/utils/__tests__/spawn.test.ts | 11 ++++++++++- packages/core/src/utils/spawn.ts | 13 +++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/server/utils/__tests__/spawn.test.ts b/app/server/utils/__tests__/spawn.test.ts index 66164c42..8066a442 100644 --- a/app/server/utils/__tests__/spawn.test.ts +++ b/app/server/utils/__tests__/spawn.test.ts @@ -206,7 +206,7 @@ describe("safeSpawn", () => { expect(result.error).toBe("err_last"); }); - test("stderr keeps the full stderr output", async () => { + test("stderr keeps the captured stderr output", async () => { const result = await safeSpawn({ command: "sh", args: ["-c", "echo err_first >&2 && echo err_last >&2 && exit 1"], @@ -215,6 +215,15 @@ describe("safeSpawn", () => { expect(result.stderr).toBe("err_first\nerr_last"); }); + test("stderr keeps only the last 50 stderr lines", async () => { + const result = await safeSpawn({ + command: "sh", + args: ["-c", "i=1; while [ $i -le 55 ]; do echo err_$i >&2; i=$((i+1)); done; exit 1"], + }); + + expect(result.stderr).toBe(Array.from({ length: 50 }, (_, index) => `err_${index + 6}`).join("\n")); + }); + test("returns exitCode -1 when the command is not found", async () => { const result = await safeSpawn({ command: "this-command-does-not-exist-zerobyte", diff --git a/packages/core/src/utils/spawn.ts b/packages/core/src/utils/spawn.ts index 5d173b23..618ee7ac 100644 --- a/packages/core/src/utils/spawn.ts +++ b/packages/core/src/utils/spawn.ts @@ -62,6 +62,8 @@ export type SpawnResult = { stderr?: string; }; +const MAX_STDERR_LINES = 50; + export function safeSpawn(params: SafeSpawnParamsLines): Promise; export function safeSpawn(params: SafeSpawnParamsRaw): Promise; export function safeSpawn(params: SafeSpawnParams): Promise { @@ -71,7 +73,7 @@ export function safeSpawn(params: SafeSpawnParams): Promise { let lastStdout = ""; let lastStderr = ""; - let stderr = ""; + const stderrLines: string[] = []; return new Promise((resolve) => { const child = spawn(command, args, { @@ -103,7 +105,10 @@ export function safeSpawn(params: SafeSpawnParams): Promise { rlErr.on("line", (line) => { if (onStderr) onStderr(line); - stderr += `${line}\n`; + stderrLines.push(line); + if (stderrLines.length > MAX_STDERR_LINES) { + stderrLines.shift(); + } const trimmed = line.trim(); if (trimmed.length > 0) { lastStderr = line; @@ -118,7 +123,7 @@ export function safeSpawn(params: SafeSpawnParams): Promise { exitCode: -1, summary: lastStdout, error: err.message || lastStderr, - stderr: stderr.trim(), + stderr: stderrLines.join("\n"), }); }); @@ -127,7 +132,7 @@ export function safeSpawn(params: SafeSpawnParams): Promise { exitCode: code ?? -1, summary: lastStdout, error: lastStderr, - stderr: stderr.trim(), + stderr: stderrLines.join("\n"), }); }); });