From d84a8b15063aa09418fe532a7ec99d04e4a93bfe Mon Sep 17 00:00:00 2001 From: ly-wang19 <94427531+ly-wang19@users.noreply.github.com> Date: Tue, 23 Jun 2026 09:28:14 +0800 Subject: [PATCH] fix(discord): reserve closing-fence space on fence-closing lines (#95661) `chunkDiscordText` reserved closing-fence space from the post-line fence state (`nextOpenFence`), but a flush during a line's segment loop appends the closing fence based on the still-open `openFence`, which is only advanced after the line. On a line that closes a fence yet carries trailing text, `reserveChars` was 0 while `flush()` still appended a `` ``` ``, producing a chunk of `maxChars + 4` (e.g. 2004 > 2000) that Discord rejects with HTTP 400. Reserve against `nextOpenFence ?? openFence` so whichever fence a flush can close is accounted for, keeping a fence-closing line's chunk within `maxChars`. Co-authored-by: ly-wang19 Co-authored-by: Claude Opus 4.8 (1M context) --- extensions/discord/src/chunk.test.ts | 11 +++++++++++ extensions/discord/src/chunk.ts | 8 ++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/extensions/discord/src/chunk.test.ts b/extensions/discord/src/chunk.test.ts index 11b28749d24b..0e9c81d15434 100644 --- a/extensions/discord/src/chunk.test.ts +++ b/extensions/discord/src/chunk.test.ts @@ -78,6 +78,17 @@ describe("chunkDiscordText", () => { } }); + it("keeps chunks within maxChars when a closing fence line carries trailing text", () => { + // A line that both closes the fence and carries a long tail must still reserve closing-fence + // space; otherwise a mid-line flush appended "```" and overflowed maxChars (e.g. 2004 > 2000). + for (let pad = 1990; pad <= 2000; pad++) { + const text = "hi\n```lang\n```" + "z".repeat(pad); + for (const chunk of chunkDiscordText(text, { maxChars: 2000, maxLines: 100 })) { + expect(chunk.length).toBeLessThanOrEqual(2000); + } + } + }); + it("preserves whitespace when splitting long lines", () => { const text = Array.from({ length: 40 }, () => "word").join(" "); const chunks = chunkDiscordText(text, { maxChars: 20, maxLines: 50 }); diff --git a/extensions/discord/src/chunk.ts b/extensions/discord/src/chunk.ts index d78050605218..952dc150c6d3 100644 --- a/extensions/discord/src/chunk.ts +++ b/extensions/discord/src/chunk.ts @@ -207,8 +207,12 @@ export function chunkDiscordText(text: string, opts: ChunkDiscordTextOpts = {}): } } - const reserveChars = nextOpenFence ? closeFenceLine(nextOpenFence).length + 1 : 0; - const reserveLines = nextOpenFence ? 1 : 0; + // A flush can fire mid-line, before `openFence` advances to `nextOpenFence` below, so it closes + // against the still-open `openFence`. A fence-closing line that also carries trailing text would + // otherwise reserve 0 yet still get a closing fence appended on flush, overflowing maxChars. + const fenceToReserve = nextOpenFence ?? openFence; + const reserveChars = fenceToReserve ? closeFenceLine(fenceToReserve).length + 1 : 0; + const reserveLines = fenceToReserve ? 1 : 0; const effectiveMaxChars = maxChars - reserveChars; const effectiveMaxLines = maxLines - reserveLines; const charLimit = effectiveMaxChars > 0 ? effectiveMaxChars : maxChars;