From b1893b3d7720273c1c2289dde88cf22e6c1a83aa Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Mon, 18 May 2026 23:05:34 +0200 Subject: [PATCH] fix(agent): preserve oversized tail output with trailing newline closes #4715 --- packages/agent/CHANGELOG.md | 4 ++++ packages/agent/src/harness/utils/truncate.ts | 1 + packages/agent/test/harness/truncate.test.ts | 11 +++++++++++ 3 files changed, 16 insertions(+) diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 7c451eb0..44fa99ee 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed tail truncation for oversized single-line output that ends with a trailing newline ([#4715](https://github.com/earendil-works/pi/issues/4715)). + ## [0.75.3] - 2026-05-18 ## [0.75.2] - 2026-05-18 diff --git a/packages/agent/src/harness/utils/truncate.ts b/packages/agent/src/harness/utils/truncate.ts index 1870f423..2372a941 100644 --- a/packages/agent/src/harness/utils/truncate.ts +++ b/packages/agent/src/harness/utils/truncate.ts @@ -218,6 +218,7 @@ export function truncateTail(content: string, options: TruncationOptions = {}): const totalBytes = utf8ByteLength(content); const lines = content.split("\n"); + if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop(); const totalLines = lines.length; // Check if no truncation needed diff --git a/packages/agent/test/harness/truncate.test.ts b/packages/agent/test/harness/truncate.test.ts index a859b321..f712cc2c 100644 --- a/packages/agent/test/harness/truncate.test.ts +++ b/packages/agent/test/harness/truncate.test.ts @@ -102,6 +102,17 @@ describe("truncate utilities", () => { expect(result.outputBytes).toBe(5); }); + it("truncates an oversized single line with a trailing newline", () => { + const input = `${"X".repeat(300_000)}\n`; + const result = truncateTail(input, { maxBytes: 1024, maxLines: 100 }); + + expect(result.content).toBe("X".repeat(1024)); + expect(result.outputBytes).toBe(1024); + expect(result.outputLines).toBe(1); + expect(result.lastLinePartial).toBe(true); + expect(result.truncatedBy).toBe("bytes"); + }); + it("drops an oversized trailing character when it cannot fit in tail byte limit", () => { const result = truncateTail("abc🙂", { maxBytes: 3, maxLines: 10 });