The homeserver rejects any event whose canonical JSON is over 65,536 bytes
(M_TOO_LARGE), but Matrix chunked outbound text on characters, 16,000 by
default with a 65,535 ceiling. Each message carries its text twice (body plus
formatted_body HTML), and an edit carries it four times (the "* " fallback
plus m.new_content).
On the default config this caused three failures:
- A formatted chunk of CJK text came to about 95 KB and was rejected, so
_send_with_retry fell back to a plain 3,500-char truncation.
- A streamed edit near the limit came to about 80 KB even for English, so
edits failed mid-stream.
- The standalone sender (cron without a running gateway) posted each
16,000-char chunk as one event, and hit the same failure on non-Latin text.
The fix measures Matrix text in UTF-8 bytes, the unit of the cap:
- The adapter's message_len_fn now returns UTF-8 bytes, so send() and the
stream consumer's split and progress-edit budgets count the same unit.
- A chunk gets a third of a 45,000-byte content budget, which leaves room for
the event envelope and E2EE's 4/3 base64 inflation. That makes 15,000 the
default and the ceiling for max_message_length, so English chunks stay
about the same size.
- _build_text_message_content drops the HTML when escape-heavy text would
push the content past the budget.
- An edit that would still be too large keeps its full m.new_content and
shortens only the top-level fallback, which edit-aware clients never
render.
- The standalone sender re-chunks on UTF-8 bytes and applies the same HTML
guard.
(cherry picked from commit cab00c94175955a68ecd4d38c5748d63148ca6be)