From 62ea5079749a5bf2af303659aef46bc06000b0d3 Mon Sep 17 00:00:00 2001 From: Nick Tabeling Date: Fri, 10 Apr 2026 10:13:58 +0200 Subject: [PATCH] fix(DIS-105): prevent fence closure from pushing chunks beyond 2000-char hard limit The flush() helper now enforces the Discord hard limit by hard-splitting any chunk that exceeds 2000 chars (e.g. when "\n```" was appended after the soft-limit check). Also reserves 4 chars of overhead in the soft-limit check when inside a fenced code block. Adds a regression test that verifies no chunk exceeds 2000 chars even when the fence closure is appended at the limit boundary. Co-Authored-By: Claude Sonnet 4.6 --- src/discord/split.ts | 11 ++++++++++- tests/unit/split-for-discord.test.ts | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/discord/split.ts b/src/discord/split.ts index 154b325..2c2515d 100644 --- a/src/discord/split.ts +++ b/src/discord/split.ts @@ -28,6 +28,12 @@ export function splitForDiscord(text: string, limit = 1900): string[] { let fenceLang = ""; const flush = () => { + // Hard-split if current exceeds the Discord hard limit (e.g. because + // a fence closure "\n```" was appended after the soft-limit check). + while (current.length > DISCORD_HARD_LIMIT) { + chunks.push(current.slice(0, DISCORD_HARD_LIMIT)); + current = current.slice(DISCORD_HARD_LIMIT); + } if (current.length > 0) { chunks.push(current); current = ""; @@ -43,7 +49,10 @@ export function splitForDiscord(text: string, limit = 1900): string[] { const separator = current.length > 0 ? "\n" : ""; const addition = separator + line; - if (current.length + addition.length > effectiveLimit) { + // Reserve space for a closing fence (``` + newline) when inside a code block + const fenceOverhead = inFence ? 4 : 0; // "\n```".length + + if (current.length + addition.length + fenceOverhead > effectiveLimit) { // Need to flush before adding this line if (inFence) { // Close the open fence before flushing diff --git a/tests/unit/split-for-discord.test.ts b/tests/unit/split-for-discord.test.ts index c7baeed..507e520 100644 --- a/tests/unit/split-for-discord.test.ts +++ b/tests/unit/split-for-discord.test.ts @@ -101,4 +101,20 @@ describe("splitForDiscord", () => { const idx = result.indexOf(closingChunk!); expect(result[idx + 1]).toMatch(/^```python/); }); + + // Regression: fence closure "\n```" must not push a chunk beyond the 2000 hard limit + it("does not exceed 2000 chars even when fence closure is appended at limit boundary", () => { + const fenceOpen = "```typescript\n"; + // Line long enough that current is near 2000 when fence closure is added + const longLine = "x".repeat(1984); + const nextLine = "y".repeat(10); + const fenceClose = "\n```"; + + const text = fenceOpen + longLine + "\n" + nextLine + fenceClose; + const result = splitForDiscord(text, 2000); + + for (const chunk of result) { + expect(chunk.length).toBeLessThanOrEqual(DISCORD_HARD_LIMIT); + } + }); });