fix(mattermost): truncate draft previews on code-point boundaries#97472
Conversation
|
Codex review: passed. Reviewed June 28, 2026, 2:18 PM ET / 18:18 UTC. Summary PR surface: Source +1, Tests +26. Total +27 across 2 files. Reproducibility: yes. Source inspection shows current main raw-slices at 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:
Next step before merge
Security Review detailsBest possible solution: Land this focused Mattermost plugin fix after exact-head checks and mergeability gates, keeping surrogate-safe truncation local to the draft normalizer and regression test. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows current main raw-slices at Is this the best way to solve the issue? Yes. Reusing the existing SDK AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against f9dddea72a7b. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +1, Tests +26. Total +27 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
|
|
@clawsweeper automerge |
|
🦞✅ Source: What merged:
Automerge notes:
The automerge loop is complete. Automerge progress:
|
… raw UTF-16
normalizeMattermostDraftText truncated the streaming preview with a raw
`trimmed.slice(0, maxChars - 3)`. When an emoji (or any astral code point)
straddled the cut, the slice kept the high surrogate but dropped its low
surrogate; trimEnd() does not strip a lone surrogate, so the Mattermost post
message carried a dangling surrogate half rendered as the replacement char.
Example: normalizeMattermostDraftText("a".repeat(8) + "\u{1F600}" + "b".repeat(5), 12)
cuts at index 9 (maxChars-3), the emoji 😀 occupies UTF-16 indices 8-9, and the
old code produced "aaaaaaaa\ud83d..." (lone high surrogate). The same defect hits
the default 4000-char stream limit at cut point 3997.
Route the slice through sliceUtf16Safe (from openclaw/plugin-sdk/text-utility-runtime),
matching the surrogate-safe truncation already used by the slack adapter, so a
straddling emoji is dropped whole: the output becomes "aaaaaaaa...". All-BMP input
and inputs at/under the limit are byte-identical to the previous behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
71f839b to
86d0dd2
Compare
…enclaw#97472) Summary: - The PR replaces Mattermost draft preview raw UTF-16 slicing with the existing SDK `sliceUtf16Safe` helper and adds a regression test for an emoji that straddles the preview limit. - PR surface: Source +1, Tests +26. Total +27 across 2 files. - Reproducibility: yes. Source inspection shows current main raw-slices at `maxChars - 3`; driving `createMatt ... at UTF-16 indices 8-9 reaches the lone-surrogate path, though I did not run tests in this read-only sweep. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(mattermost): truncate draft previews on code-point boundaries Validation: - ClawSweeper review passed for head 86d0dd2. - Required merge gates passed before the squash merge. Prepared head SHA: 86d0dd2 Review: openclaw#97472 (comment) Co-authored-by: ly-wang19 <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman
…enclaw#97472) Summary: - The PR replaces Mattermost draft preview raw UTF-16 slicing with the existing SDK `sliceUtf16Safe` helper and adds a regression test for an emoji that straddles the preview limit. - PR surface: Source +1, Tests +26. Total +27 across 2 files. - Reproducibility: yes. Source inspection shows current main raw-slices at `maxChars - 3`; driving `createMatt ... at UTF-16 indices 8-9 reaches the lone-surrogate path, though I did not run tests in this read-only sweep. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(mattermost): truncate draft previews on code-point boundaries Validation: - ClawSweeper review passed for head 86d0dd2. - Required merge gates passed before the squash merge. Prepared head SHA: 86d0dd2 Review: openclaw#97472 (comment) Co-authored-by: ly-wang19 <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman
…enclaw#97472) Summary: - The PR replaces Mattermost draft preview raw UTF-16 slicing with the existing SDK `sliceUtf16Safe` helper and adds a regression test for an emoji that straddles the preview limit. - PR surface: Source +1, Tests +26. Total +27 across 2 files. - Reproducibility: yes. Source inspection shows current main raw-slices at `maxChars - 3`; driving `createMatt ... at UTF-16 indices 8-9 reaches the lone-surrogate path, though I did not run tests in this read-only sweep. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(mattermost): truncate draft previews on code-point boundaries Validation: - ClawSweeper review passed for head 86d0dd2. - Required merge gates passed before the squash merge. Prepared head SHA: 86d0dd2 Review: openclaw#97472 (comment) Co-authored-by: ly-wang19 <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman
…enclaw#97472) Summary: - The PR replaces Mattermost draft preview raw UTF-16 slicing with the existing SDK `sliceUtf16Safe` helper and adds a regression test for an emoji that straddles the preview limit. - PR surface: Source +1, Tests +26. Total +27 across 2 files. - Reproducibility: yes. Source inspection shows current main raw-slices at `maxChars - 3`; driving `createMatt ... at UTF-16 indices 8-9 reaches the lone-surrogate path, though I did not run tests in this read-only sweep. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(mattermost): truncate draft previews on code-point boundaries Validation: - ClawSweeper review passed for head 86d0dd2. - Required merge gates passed before the squash merge. Prepared head SHA: 86d0dd2 Review: openclaw#97472 (comment) Co-authored-by: ly-wang19 <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman (cherry picked from commit 881ec2f)
…enclaw#97472) Summary: - The PR replaces Mattermost draft preview raw UTF-16 slicing with the existing SDK `sliceUtf16Safe` helper and adds a regression test for an emoji that straddles the preview limit. - PR surface: Source +1, Tests +26. Total +27 across 2 files. - Reproducibility: yes. Source inspection shows current main raw-slices at `maxChars - 3`; driving `createMatt ... at UTF-16 indices 8-9 reaches the lone-surrogate path, though I did not run tests in this read-only sweep. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(mattermost): truncate draft previews on code-point boundaries Validation: - ClawSweeper review passed for head 86d0dd2. - Required merge gates passed before the squash merge. Prepared head SHA: 86d0dd2 Review: openclaw#97472 (comment) Co-authored-by: ly-wang19 <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman
…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
Mattermost draft preview truncation used raw UTF-16 slicing. If an emoji or other astral character straddled the draft preview limit, the draft update could contain a dangling surrogate half before the ellipsis.
Why This Change Was Made
The Mattermost draft text normalizer now uses the existing plugin SDK
sliceUtf16Safehelper while preserving the existing max-character cap and trailing ellipsis behavior.User Impact
Long Mattermost draft updates containing emoji no longer produce malformed replacement characters at truncation boundaries.
Evidence
Autoreview result: clean; no accepted/actionable findings.
After-fix Proof
Boundary probe on this PR branch:
{ "usesSafeSlice": true, "output": "aaaaaaaa...", "outputLength": 11, "hasLoneSurrogate": false }The probe places an emoji across the
maxChars - 3slice boundary. The after-fix output drops the emoji whole and leaves no lone UTF-16 surrogate.