Skip to content

fix(dingtalk): split oversized markdown lines - #5299

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
tt-a1i:fix/dingtalk-split-long-lines
Jun 18, 2026
Merged

wenshao merged 1 commit into
QwenLM:mainfrom
tt-a1i:fix/dingtalk-split-long-lines

Conversation

@tt-a1i

@tt-a1i tt-a1i commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Split DingTalk markdown lines that exceed the 3800 character chunk limit.
  • Preserve plain-text newlines around split long lines.
  • Keep code fences closed and reopened across chunk boundaries, including long code lines and fence delimiter boundary cases.

Test Plan

  • npx vitest run --config vitest.config.ts from packages/channels/dingtalk
  • npm run build --workspace=@qwen-code/channel-dingtalk
  • npx eslint packages/channels/dingtalk/src/markdown.ts packages/channels/dingtalk/src/markdown.test.ts
  • npx prettier --check packages/channels/dingtalk/src/markdown.ts packages/channels/dingtalk/src/markdown.test.ts
  • git diff --check
  • subagent review: no blockers

AI Assistance Disclosure

I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.

@wenshao

wenshao commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao

wenshao commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

✅ Local verification report — PR #5299 fix(dingtalk): split oversized markdown lines

Verdict: strong LGTM — fixes a real, broad correctness bug with no regressions. On main, splitChunks only breaks between whole lines, so any single line longer than CHUNK_LIMIT (3800) overflows into a chunk DingTalk will reject/truncate. This PR splits oversized lines, preserves newlines, and keeps code fences closed/reopened across chunk boundaries — and it holds up across the PR's tests, a 2,200-case fuzz, and adversarial edge cases.

How it was verified

Real vitest under tmux in two isolated detached worktrees (pr5299-head = 2ab8661b, origin/main = 905041f9), repo node_modules symlinked. Env: Node v22.22.2, vitest 3.2.4. The dingtalk package is byte-identical between merge-base and current main; merges cleanly.

Check Result
PR full suite (markdown.test.ts) ✅ 26/26 pass
BASE as-is ✅ 17 pass (PR adds 9 new tests)
Genuine-regression — PR's test file on BASE source ✅ all 9 new tests FAIL (e.g. 5000-char line → base returns 1 chunk, expected 1 to be 2), 17 originals still pass → tests truly catch the bug
tsc --build (npm run build) ✅ exit 0
eslint markdown.ts + test ✅ exit 0
prettier --check ✅ clean
git diff --check ✅ exit 0

Property/fuzz A/B (2,200 diverse inputs, seeded & reproducible)

Generators: plain multi-line text, single long lines, boundary-straddling lines, inline-backtick plain text, well-formed fenced code blocks, mixed docs, and fence-delimiter-straddle cases. Invariants: every chunk ≤ 3800 (LEN); plain text reconstructs exactly (PLAIN_EXACT); non-fence payload preserved (PAYLOAD); only paired fences injected (BACKTICK_PARITY, Δ%6==0); no newline loss (NEWLINE); balanced fences per chunk for well-formed inputs (FENCE_BALANCE).

Invariant violations PR (after) main (before)
LEN (chunk > 3800) 0 1,160
PLAIN_EXACT (lossless plain text) 0 588
NEWLINE (no \n dropped) 0 876
PAYLOAD / BACKTICK_PARITY / FENCE_BALANCE 0 0
max chunk length produced 3,800 11,987 (3.2× the limit)

The PR packs right up to 3800 (no over-conservative splitting) while never exceeding it; 1,003 code-fence boundary injections were exercised. The invariants are discriminating (main fails LEN/PLAIN_EXACT/NEWLINE, PR passes all), so the green result is meaningful, not vacuous. Note main also silently drops the \n at every chunk boundary for multi-line text — this PR fixes that too.

Adversarial edge cases (on the compiled dist, 30 s timeout guard)

All terminate instantly with no chunk > 3800:

Input chunks max len
8000 backticks, no newline 3 3798
2000 ``` fence-triples 2 3798
```+5000 backticks+``` 2 3798
exactly 3800 / 3801 1 / 2 3800 / 3800
fence at exact boundary 3 3800
single 200,000-char line 53 3800

No infinite loops (each available<=0/pieceLength===0 path flushes and always recovers available>0).

Reproduce

git worktree add --detach /tmp/wt-after  $(gh pr view 5299 --json headRefOid -q .headRefOid)
git worktree add --detach /tmp/wt-before origin/main
for w in wt-after wt-before; do ln -s "$PWD/node_modules" /tmp/$w/node_modules; done
cd /tmp/wt-after/packages/channels/dingtalk
../../../../node_modules/.bin/vitest run --config vitest.config.ts src/markdown.test.ts   # 26/26
# Prove the tests catch the bug: run PR's test on BASE source -> 9 fail
🇨🇳 中文版(点击展开)

✅ 本地验证报告 — PR #5299 fix(dingtalk): split oversized markdown lines

结论:强烈建议合并 —— 修复了一个真实且影响面较广的正确性 bug,且无回归。 在 main 上,splitChunks 只在整行之间切分,所以任何单行超过 CHUNK_LIMIT(3800)就会溢出,产生 DingTalk 会拒绝/截断的超长 chunk。本 PR 会切分超长行、保留换行,并在 chunk 边界正确闭合/重开代码围栏(fence)。它在 PR 自带测试、2200 例 fuzz、以及一批极端输入下都站得住脚。

验证方式

在 tmux 中、两个隔离的 detached worktree(pr5299-head = 2ab8661b,origin/main = 905041f9)里运行真实 vitest,软链接复用仓库 node_modules。环境:Node v22.22.2、vitest 3.2.4。dingtalk 包在 merge-base 与当前 main 之间字节一致;可干净合并。

检查项 结果
PR 全量测试(markdown.test.ts) ✅ 26/26 通过
BASE 原样 ✅ 17 通过(PR 新增 9 个测试)
回归测试有效性 —— 用 PR 的测试跑 BASE 源码 ✅ 9 个新测试全部失败(如 5000 字符单行:base 只返回 1 个 chunk,expected 1 to be 2),原有 17 个仍通过 → 说明测试确实抓到了 bug
tsc --build(npm run build) ✅ exit 0
eslint markdown.ts + 测试 ✅ exit 0
prettier --check ✅ 通过
git diff --check ✅ exit 0

属性/fuzz 对比(2200 个多样化输入,固定种子可复现)

生成器涵盖:纯多行文本、超长单行、边界附近的长行、含内联反引号的纯文本、规范的代码围栏块、混合文档、以及围栏分隔符跨边界的情况。不变量:每个 chunk ≤ 3800(LEN);纯文本可精确还原(PLAIN_EXACT);非围栏内容不丢失(PAYLOAD);只注入成对围栏(BACKTICK_PARITY,Δ%6==0);不丢换行(NEWLINE);规范输入下每个 chunk 围栏平衡(FENCE_BALANCE)。

不变量违反数 PR(修复后) main(修复前)
LEN(chunk > 3800) 0 1160
PLAIN_EXACT(纯文本无损) 0 588
NEWLINE(不丢 \n) 0 876
PAYLOAD / BACKTICK_PARITY / FENCE_BALANCE 0 0
产生的最大 chunk 长度 3800 11987(超限 3.2 倍)

PR 会一直填到 3800(不会过度保守地提前切分)但绝不超过;过程中触发了 1003 次代码围栏边界注入。这些不变量是有区分度的(main 在 LEN/PLAIN_EXACT/NEWLINE 上失败、PR 全通过),所以“全绿”是有意义的、并非空跑。另外 main 在多行文本的每个 chunk 边界还会悄悄丢掉一个 \n,本 PR 一并修复了。

极端输入(在编译产物 dist 上跑,30 秒超时保护)

全部瞬间结束、且没有任何 chunk > 3800:

输入 chunk 数 最大长度
8000 个反引号、无换行 3 3798
2000 个 ``` 三连 2 3798
```+5000 反引号+``` 2 3798
恰好 3800 / 3801 1 / 2 3800 / 3800
围栏正好落在边界 3 3800
单行 200000 字符 53 3800

无死循环(每个 available<=0 / pieceLength===0 分支都会 flush 并恢复到 available>0)。


Verified locally with real vitest runs under tmux on isolated worktrees of the PR head and origin/main; bug breadth quantified by a 2,200-case seeded property fuzz (A/B), robustness checked with adversarial all-backtick / 200k-char inputs on the compiled dist.

@wenshao

wenshao commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao
wenshao merged commit 06fdd59 into QwenLM:main Jun 18, 2026
33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants