fix: strip leading whitespace from sanitizeUserFacingText output (#16158)

* fix: strip leading whitespace from sanitizeUserFacingText output

LLM responses frequently begin with \n\n, which survives through
sanitizeUserFacingText and reaches the channel as visible blank lines.

Root cause: the function used trimmed text for empty-checks but returned
the untrimmed 'stripped' variable. Two one-line fixes:
1. Return empty string (not whitespace-only 'stripped') for blank input
2. Apply trimStart() to the final return value

Fixes the same issue as #8052 and #10612 but at the root cause
(sanitizeUserFacingText) rather than scattering trimStart across
multiple delivery paths.

* Changelog: note sanitizeUserFacingText whitespace normalization

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Jake
2026-02-15 04:23:05 +13:00
committed by GitHub
parent e3b432e481
commit 3881af5b37
3 changed files with 24 additions and 2 deletions

View File

@@ -69,4 +69,25 @@ describe("sanitizeUserFacingText", () => {
const text = "Hello there!\n\nDifferent line.";
expect(sanitizeUserFacingText(text)).toBe(text);
});
it("strips leading newlines from LLM output", () => {
expect(sanitizeUserFacingText("\n\nHello there!")).toBe("Hello there!");
expect(sanitizeUserFacingText("\nHello there!")).toBe("Hello there!");
expect(sanitizeUserFacingText("\n\n\nMultiple newlines")).toBe("Multiple newlines");
});
it("strips leading whitespace and newlines combined", () => {
expect(sanitizeUserFacingText("\n \n Hello")).toBe("Hello");
expect(sanitizeUserFacingText(" \n\nHello")).toBe("Hello");
});
it("preserves trailing whitespace and internal newlines", () => {
expect(sanitizeUserFacingText("Hello\n\nWorld\n")).toBe("Hello\n\nWorld\n");
expect(sanitizeUserFacingText("Line 1\nLine 2")).toBe("Line 1\nLine 2");
});
it("returns empty for whitespace-only input", () => {
expect(sanitizeUserFacingText("\n\n")).toBe("");
expect(sanitizeUserFacingText(" \n ")).toBe("");
});
});

View File

@@ -488,7 +488,7 @@ export function sanitizeUserFacingText(text: string, opts?: { errorContext?: boo
const stripped = stripFinalTagsFromText(text);
const trimmed = stripped.trim();
if (!trimmed) {
return stripped;
return "";
}
// Only apply error-pattern rewrites when the caller knows this text is an error payload.
@@ -527,7 +527,7 @@ export function sanitizeUserFacingText(text: string, opts?: { errorContext?: boo
}
}
return collapseConsecutiveDuplicateBlocks(stripped);
return collapseConsecutiveDuplicateBlocks(stripped).trimStart();
}
export function isRateLimitAssistantError(msg: AssistantMessage | undefined): boolean {