fix(telegram): clip progress text on code-point boundaries to avoid lone surrogates#96456
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 24, 2026, 10:14 AM ET / 14:14 UTC. Summary PR surface: Source +13, Tests +48. Total +61 across 3 files. Reproducibility: yes. A focused Node probe against current-main clipping logic reproduces a dangling high surrogate when an emoji straddles the 299/300 UTF-16 code-unit cut. 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:
Mantis proof suggestion Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the focused helper reuse after required CI and maintainer review, optionally collecting Telegram Desktop proof for the visible progress draft path. Do we have a high-confidence way to reproduce the issue? Yes. A focused Node probe against current-main clipping logic reproduces a dangling high surrogate when an emoji straddles the 299/300 UTF-16 code-unit cut. Is this the best way to solve the issue? Yes. Reusing the existing AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against acc2a0ee7297. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +13, Tests +48. Total +61 across 3 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
|
…one surrogates clipProgressMarkdownText used text.slice(0, MAX - 1), which splits UTF-16 surrogate pairs. When an astral character (e.g. an emoji) straddles the 299/300-code-unit boundary, the slice keeps only the high surrogate half (\uD83D), producing an invalid character that the Telegram Bot API rejects. Extract the helper to extensions/telegram/src/truncate.ts as clipTelegramProgressText and switch to sliceUtf16Safe (from openclaw/plugin-sdk/text-utility-runtime), mirroring the fix applied to the equivalent Slack helper in openclaw#96382. Adds truncate.test.ts with five vitest cases covering the straddle, fits-before-cut, unchanged-short, trimEnd, and ASCII-exact-limit paths.
acb9de5 to
765d6c0
Compare
|
Merged via squash.
Thanks @he-yufeng! |
…one surrogates (openclaw#96456) Merged via squash. Prepared head SHA: 765d6c0 Co-authored-by: he-yufeng <[email protected]> Co-authored-by: vincentkoc <[email protected]> Reviewed-by: @vincentkoc
…one surrogates (openclaw#96456) Merged via squash. Prepared head SHA: 765d6c0 Co-authored-by: he-yufeng <[email protected]> Co-authored-by: vincentkoc <[email protected]> Reviewed-by: @vincentkoc
…void lone surrogates The Mattermost inbound handler in extensions/mattermost/src/mattermost/monitor.ts truncates the message body preview with bodyText.slice(0, 200), which can split a UTF-16 surrogate pair when an emoji (e.g. 🎉, 🦞) or other astral character lands at the 200-char boundary. The resulting lone surrogate half leaks into the verbose log preview and could propagate to downstream consumers that reject ill-formed strings. Replace the raw .slice(0, 200) with the shared truncateUtf16Safe helper (from openclaw/plugin-sdk/text-utility-runtime), which backs up one code unit when the cut would land inside a surrogate pair. This matches the same pattern already applied to the Slack, Discord, Telegram, IRC, Line, MS Teams, and Feishu channel previews (openclaw#96456, openclaw#96569, openclaw#96572, openclaw#96577, openclaw#96578, openclaw#97462, openclaw#97472, openclaw#98994). The preview is only used for the verbose inbound log line so the behavioral change is cosmetic: when the 200th code unit would split an emoji, the preview now includes one fewer code unit rather than emitting a broken surrogate.
…void lone surrogates The Mattermost inbound handler in extensions/mattermost/src/mattermost/monitor.ts truncates the message body preview with bodyText.slice(0, 200), which can split a UTF-16 surrogate pair when an emoji (e.g. 🎉, 🦞) or other astral character lands at the 200-char boundary. The resulting lone surrogate half leaks into the verbose log preview and could propagate to downstream consumers that reject ill-formed strings. Replace the raw .slice(0, 200) with the shared truncateUtf16Safe helper (from openclaw/plugin-sdk/text-utility-runtime), which backs up one code unit when the cut would land inside a surrogate pair. This matches the same pattern already applied to the Slack, Discord, Telegram, IRC, Line, MS Teams, and Feishu channel previews (openclaw#96456, openclaw#96569, openclaw#96572, openclaw#96577, openclaw#96578, openclaw#97462, openclaw#97472, openclaw#98994). The preview is only used for the verbose inbound log line so the behavioral change is cosmetic: when the 200th code unit would split an emoji, the preview now includes one fewer code unit rather than emitting a broken surrogate.
What Problem This Solves
Fixes an issue where users sending a Telegram progress update containing an emoji (or any astral Unicode character) that straddles the 300-code-unit progress-text limit would cause the bot to emit an invalid character in the Telegram Bot API payload.
clipProgressMarkdownTextclipped withtext.slice(0, 299). JavaScript.sliceoperates on UTF-16 code units, so a surrogate pair exactly at that boundary is split in two — the high surrogate\uD83Dis kept but the matching low surrogate is cut off. A lone high surrogate is not valid UTF-8/UTF-16 and Telegram's API rejects messages containing one.This affects every progress line rendered via
formatProgressAsMarkdownCode,renderTelegramProgressStringLine, and the detail field ofrenderTelegramProgressLine.Why This Change Was Made
This is the same class of bug fixed in the Slack plugin by #96382. That PR introduced
sliceUtf16Safeinopenclaw/plugin-sdk/text-utility-runtime, which steps back one code unit when the cut falls on a high surrogate, ensuring the pair is dropped whole rather than split.The Telegram progress clipper was not updated alongside the Slack fix.
User Impact
Users whose Telegram bots display progress text (e.g. tool-call output, reasoning steps) could see rejected Bot API calls or garbled
?characters when an emoji appears near the 300-char cutoff.Evidence
Node.js reproduction of the broken behavior (before fix):
With
sliceUtf16Safe:Five vitest cases in
extensions/telegram/src/truncate.test.tscover: the straddle (lone-surrogate) case, an emoji that fits entirely before the cut, an unchanged short string, trailing-whitespace trimming, and plain ASCII at the exact limit.CI is the authoritative test runner; the test logic was verified with Node.js manually.