fix(discord): long code-fence replies can exceed the message length limit#110148
Conversation
When a chunk is flushed mid code-fence, the next chunk reopens the fence with the full original opening line (info string included), but the reserve accounting only budgets the closing marker. A long fence opening line therefore pushed reopened continuation chunks past maxChars, which Discord rejects with HTTP 400. Reopen continuation chunks with a bare marker when the full opening line would not leave room for the close and body, and size split segments against the reopen prefix so no chunk exceeds maxChars. This mirrors the existing closing-fence-overflow guard on the opening/reopen side.
|
Codex review: needs maintainer review before merge. Reviewed July 18, 2026, 5:54 PM ET / 21:54 UTC. Summary PR surface: Source +37, Tests +64. Total +101 across 2 files. Reproducibility: yes. from source and supplied exact-head runtime evidence: a fenced reply with an extremely long opening line causes current-main continuation chunks to exceed the 2,000-character limit, while the PR head keeps emitted chunks within the limit and preserves the body. Review metrics: none identified. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Rebase or refresh the clean merge result against current Do we have a high-confidence way to reproduce the issue? Yes from source and supplied exact-head runtime evidence: a fenced reply with an extremely long opening line causes current-main continuation chunks to exceed the 2,000-character limit, while the PR head keeps emitted chunks within the limit and preserves the body. Is this the best way to solve the issue? Yes, subject to the owner’s normal review of the extreme-input rendering tradeoff: accounting for the continuation reopen prefix at the chunker boundary is narrower and more maintainable than adding Discord-send retries or post-hoc truncation. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 87de81a5fd4a. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +37, Tests +64. Total +101 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (9 earlier review cycles; latest 8 shown)
|
Real behavior proof (runtime output)Per the ClawSweeper ask: direct runtime invocation of the shipped public API ( Input: a fenced block whose opening line is long ( On Harness used (temporary, not committed)// extensions/discord/src/chunk.proof.mts
import { chunkDiscordTextWithMode as head } from "./chunk.ts";
import { chunkDiscordTextWithMode as main } from "./chunk.mainproof.ts"; // git show HEAD~1:extensions/discord/src/chunk.ts
const MAX = 2000;
const fence = "```";
const text = `${fence}${"a".repeat(2100)}\n${"body line\n".repeat(40)}${fence}`;
function report(label, fn) {
const chunks = fn(text, { maxChars: MAX });
const lens = chunks.map((c) => c.length);
const over = lens.filter((n) => n > MAX);
console.log(`[${label}]`);
console.log(` emitted chunks : ${chunks.length}`);
console.log(` lengths : ${lens.slice(0, 6).join(", ")}${lens.length > 6 ? ", ..." : ""}`);
console.log(` max length : ${Math.max(...lens)} (limit ${MAX})`);
console.log(` over limit : ${over.length} chunk(s)`);
console.log(` fence-reopened : ${chunks.filter((c) => c.startsWith(fence)).length} chunk(s)\n`);
}
report("current main", main);
report("PR head", head);Both temp files were deleted after the run; the working tree is clean at Committed suite on this head is green as well: Note on CI: the single red job ( |
…ce-reopen-overflow
…ce-reopen-overflow
…ce-reopen-overflow
Co-authored-by: Yigtwxx <[email protected]>
…ce-reopen-overflow
…ce-reopen-overflow
|
Merged via squash.
|
…imit (openclaw#110148) * fix(discord): reopening a long code fence overflows maxChars When a chunk is flushed mid code-fence, the next chunk reopens the fence with the full original opening line (info string included), but the reserve accounting only budgets the closing marker. A long fence opening line therefore pushed reopened continuation chunks past maxChars, which Discord rejects with HTTP 400. Reopen continuation chunks with a bare marker when the full opening line would not leave room for the close and body, and size split segments against the reopen prefix so no chunk exceeds maxChars. This mirrors the existing closing-fence-overflow guard on the opening/reopen side. * fix(discord): preserve code after fence reopen Co-authored-by: Yigtwxx <[email protected]> * fix(discord): preserve limits when fences cannot fit Co-authored-by: Yigtwxx <[email protected]> --------- Co-authored-by: Peter Steinberger <[email protected]>
What Problem This Solves
Fixes an issue where a Discord agent reply containing a fenced code block could be split into a message that exceeds Discord's 2000-character limit, so Discord rejects the send with HTTP 400 and the reply is dropped. It happens when the code block's opening fence line is long (for example a long or garbled info/language string): the chunker reopens the block on every continuation message with the full opening line.
Why This Change Was Made
chunkDiscordTextkeeps fenced blocks balanced by reopening the fence at the start of each continuation chunk, but the reserve accounting only budgeted the closing marker — never the reopened opening line. The fix reopens continuation chunks with a bare fence marker when the full opening line would not leave room for the closing marker plus body, and sizes split segments against the reopen prefix, so no emitted chunk can exceedmaxChars. The first chunk still carries the language for highlighting; only continuation chunks (which are separate Discord messages) degrade. This is the opening/reopen-side counterpart to the existing closing-fence overflow guard in the same file.User Impact
Long code-fence replies are now always split into messages that stay within Discord's limit and send successfully, instead of failing silently. No behavior change for normal-length fences.
Evidence
Real behavior proof (runtime output)
Rerun on the exact current head
5e9eb5fa29a, which includes both of @steipete's follow-ups(
preserve code after fence reopenandpreserve limits when fences cannot fit), so this transcript covers both what the PR originally fixed (over-limit payloads) and whatthat commit added (the body after a reopened fence staying on its own line).
Direct runtime invocation of the shipped public API (
chunkDiscordTextWithMode) againstmaxChars: 2000(Discord's message limit). Input: a fenced block whose opening line is 1995 chars— long enough that repeating it verbatim leaves no room for a closing fence — followed by a 40-line
body. Pre-PR source is materialized with
git show <merge-base>:extensions/discord/src/chunk.ts, soboth runs execute the same harness against real code.
Two independent failures on base, both fixed on head:
fence line where Discord parses it as the info string rather than as content. This is precisely
the case
02da4bf28c6repaired; on head every reopened fence line is either the original openingline or a bare marker, and the body round-trips exactly.
The 1534-vs-4 chunk count is the same root cause: re-prepending a 1995-char opening line after every
flush leaves only a few characters per message for actual code.
Harness (dropped after the run)
Run:
./node_modules/.bin/tsx chunk-fence.proof.mts, once on the head and once withgit show $(git merge-base HEAD origin/main):extensions/discord/src/chunk.tswritten over thesource file.
Tests and checks
main:chunkDiscordText("+ "" + `" + "a".repeat(2100) + "\n<body>\n` + "" +", { maxChars: 2000 })emits chunks of 2108–2109 chars (> 2000).maxCharstest) that fail onmainand pass with this change; the fullextensions/discord/src/chunk.test.tssuite passes (19/19).maxChars ∈ {20,50,120,500,2000}, marker widths, opening-line lengths, and body sizes confirmed no chunk exceedsmaxChars(removed before commit).tsgo(extensions + extension tests),oxfmt,oxlint, and the max-lines ratchet are all clean.Allow edits from maintainers is enabled.