Skip to content

fix: bound rendered telegram chunks - #970

Merged
aaronjmars merged 1 commit into
aeonfun:mainfrom
Svector-anu:fix/notify-telegram-chunk-limit-2026-08-26
Aug 27, 2026
Merged

fix: bound rendered telegram chunks#970
aaronjmars merged 1 commit into
aeonfun:mainfrom
Svector-anu:fix/notify-telegram-chunk-limit-2026-08-26

Conversation

@Svector-anu

Copy link
Copy Markdown
Contributor

What

Bound Telegram notification chunks after Markdown has been rendered to HTML, while keeping every emitted chunk's HTML tags balanced.

Why

The existing path split the Markdown first and rendered each chunk afterward. HTML escaping and anchor tags can expand a source chunk beyond the formatter's 3,900-character Telegram limit.

Using the current upstream code with 300 valid Markdown links reproduced rendered chunks of 4,521, 4,488, and 4,381 characters. A skill controls its notification Markdown and can therefore produce an oversized Telegram payload that the delivery path cannot send successfully.

Fix

  • Render Markdown to Telegram HTML before the final size split.
  • Split rendered HTML using the actual payload length.
  • Close and reopen active tags at chunk boundaries so each chunk remains independently valid.
  • Reserve room for the [i/N] footer.
  • Add a regression test using the same 300-link input.

Verification

  • python3 scripts/tests/test_notify_format.py — 26/26 passed.
  • bash scripts/tests/test_notify.sh — all notification integration checks passed.
  • python3 -m py_compile scripts/notify_format.py scripts/tests/test_notify_format.py — passed.
  • git diff --check — passed.

Mutation test, performed by reverting only the source fix while keeping the regression test:

FAIL: test_telegram_rendered_chunks_stay_within_limit_after_html_expansion
AssertionError: 4521 not less than or equal to 3900
Ran 26 tests
FAILED (failures=1)

Restoring the source fix returned the full formatter and notification integration suites to green. The additional generated-input stress check was local-only and is not presented as committed test coverage.

@aaronjmars

Copy link
Copy Markdown
Collaborator

Thanks for this - the chunker itself is genuinely correct (fuzzed it: 0 tag imbalances, entities intact, <pre><code class=...> reopened with attributes, no empty chunks). But I don't think it should land as-is, because it targets a measurement artifact rather than a real failure:

  • Telegram's 4096 cap is on parsed/visible text, not raw HTML - "1-4096 characters after entities parsing". Tag markup and <a href> URLs don't count. In your own 300-link repro, the "oversized" ~4.5k-char rendered chunk has only ~236 chars of Telegram-visible text, so the old chunks would have sent fine. The mutation test asserts a raw len() bound, not an actual API rejection.
  • ~No net benefit over the current path: the existing code already bounds visible length (it chunks the Markdown source to <=3400, so visible <=3400 < 4096) and already renders each chunk independently, so chunks are already tag-balanced and fence-safe. The rewrite only bounds raw length (which Telegram ignores) and introduces mid-word splits at boundaries (old code broke on paragraph/line ends).
  • The one real length mismatch is left untouched: len() counts code points, Telegram counts UTF-16 units. 4000 astral emoji = 3875 code points but 7743 UTF-16 units (> 4096). Both old and new miss this equally.

To justify merging, could you show an actual Telegram API rejection from the current code, and address the UTF-16 case? Otherwise I'll lean toward closing this. Appreciate the thoroughness either way.

@aaronjmars
aaronjmars merged commit fa11d48 into aeonfun:main Aug 27, 2026
2 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