Skip to content

fix(matrix): truncate thread starter body on code-point boundaries#97121

Merged
vincentkoc merged 1 commit into
openclaw:mainfrom
Bartok9:salvage/96407-matrix-codepoint-v2
Jun 26, 2026
Merged

fix(matrix): truncate thread starter body on code-point boundaries#97121
vincentkoc merged 1 commit into
openclaw:mainfrom
Bartok9:salvage/96407-matrix-codepoint-v2

Conversation

@Bartok9

@Bartok9 Bartok9 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Matrix thread-starter previews truncate long bodies by raw UTF-16 code-unit slice. If the boundary lands inside an astral character such as an emoji, the preview keeps a lone surrogate and renders mojibake in the agent's thread context.

Why This Change Was Made

truncateThreadStarterBody is the canonical producer of the bounded thread-starter body. This reuses the existing public sliceUtf16Safe helper (via openclaw/plugin-sdk/text-utility-runtime, the same path Discord/Slack/iMessage already use) so the cut backs up to a valid surrogate boundary while preserving the 500-code-unit limit and ... suffix.

User Impact

Long Matrix thread starters no longer split emoji or other astral characters at the truncation boundary. Short bodies and BMP-only text retain their existing output.

Evidence

  • Current main reproduces the defect: body "a"×496 + 😀 (U+1F600) + "bcd", MAX_THREAD_STARTER_BODY_LENGTH=500, so the cut at 497 lands mid-pair and leaves a lone high surrogate (U+D83D) before the ellipsis.
  • Regression test fails against pre-fix source, passes with the fix. Verified by reverting only the source change and rerunning: the new test goes red (× truncates long thread starter bodies on code-point boundaries), then green once the fix is restored.
  • Focused Matrix suite on this branch: 6/6 passed (node scripts/run-vitest.mjs extensions/matrix/src/matrix/monitor/thread-context.test.ts).
  • git diff --check clean.

Scope

Exactly 2 files, +19/-1thread-context.ts and its regression test. No unrelated changes.

Provenance and Credit

Clean re-creation of #96407 / #96961 (both auto-closed — #96961 by the dirty-branch guard after an unrelated commit contaminated the branch). This branch is rebased fresh onto current main with only the Matrix two-file fix. Preserves @ly-wang19's original authorship; rebased by @Bartok9.

Matrix thread-starter previews truncated long bodies by raw UTF-16
slice, which could cut an astral character (e.g. emoji) and leave a lone
surrogate, rendering mojibake in the agent's thread context.

Reuse the existing sliceUtf16Safe helper so the cut backs up to a valid
surrogate boundary, preserving the 500-code-unit limit and '...' suffix.
Adds a regression test that fails against the raw-slice implementation.

Salvages the original fix from openclaw#96407 (auto-closed by the active-PR
queue cap). Preserves @ly-wang19's authorship; rebased clean onto main
by @Bartok9.
@openclaw-barnacle openclaw-barnacle Bot added channel: matrix Channel integration: matrix size: XS labels Jun 26, 2026
@clawsweeper

clawsweeper Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 26, 2026, 7:20 PM ET / 23:20 UTC.

Summary
This PR changes Matrix thread-starter truncation to use the existing UTF-16-safe SDK slice helper and adds a regression test for emoji-boundary truncation.

PR surface: Source +1, Tests +17. Total +18 across 2 files.

Reproducibility: yes. Current main's exact raw slice can be exercised with a 496-a plus emoji plus bcd Matrix message body; the read-only Node check showed the dangling d83d surrogate at the truncation boundary.

Review metrics: none identified.

Root-cause cluster
Relationship: canonical
Canonical: #97121
Summary: This PR is the clean active Matrix-only replacement for the same closed surrogate-boundary fix attempts; the agents truncation PR is the same bug class at a different code site.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Next step before merge

  • [P2] No repair lane is needed because the PR is already a narrow valid fix; maintainers can finish normal review and CI gating.

Security
Cleared: Cleared: the diff imports an existing SDK text helper and adds a focused test, with no dependency, workflow, secret, package, or code-execution surface.

Review details

Best possible solution:

Land this Matrix-only fix after required CI is green, keeping the existing SDK helper as the canonical UTF-16 boundary primitive.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main's exact raw slice can be exercised with a 496-a plus emoji plus bcd Matrix message body; the read-only Node check showed the dangling d83d surrogate at the truncation boundary.

Is this the best way to solve the issue?

Yes. The changed function is the single producer of the bounded Matrix thread-starter body, and using the already exported SDK helper matches existing channel truncation patterns without adding a new API.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against bdd365a34882.

Label changes

Label changes:

  • add P2: The PR fixes a bounded Matrix channel context rendering bug with limited blast radius.
  • add proof: sufficient: Contributor real behavior proof is sufficient. Sufficient terminal proof exists in the linked predecessor PR for the same Matrix event shape, and this PR is a clean two-file re-creation of that fix.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): Sufficient terminal proof exists in the linked predecessor PR for the same Matrix event shape, and this PR is a clean two-file re-creation of that fix.

Label justifications:

  • P2: The PR fixes a bounded Matrix channel context rendering bug with limited blast radius.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): Sufficient terminal proof exists in the linked predecessor PR for the same Matrix event shape, and this PR is a clean two-file re-creation of that fix.
  • proof: sufficient: Contributor real behavior proof is sufficient. Sufficient terminal proof exists in the linked predecessor PR for the same Matrix event shape, and this PR is a clean two-file re-creation of that fix.
Evidence reviewed

PR surface:

Source +1, Tests +17. Total +18 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 2 1 +1
Tests 1 17 0 +17
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 19 1 +18

What I checked:

  • Current main source still has the bug: truncateThreadStarterBody returns value.slice(0, MAX_THREAD_STARTER_BODY_LENGTH - 3) before appending ..., so a 497-code-unit cut can leave a dangling surrogate. (extensions/matrix/src/matrix/monitor/thread-context.ts:20, bdd365a34882)
  • Source-level reproduction check: A read-only Node check with 496 as, U+1F600, and bcd produced length 500, boundary charCode d83d, and a surrogate-regex match under the current raw-slice algorithm. (bdd365a34882)
  • Existing helper contract: sliceUtf16Safe adjusts slice edges away from dangling high/low surrogate halves before delegating to String.prototype.slice. (src/utils.ts:80, bdd365a34882)
  • Extension boundary allows this import: The openclaw/plugin-sdk/text-utility-runtime subpath is exported in package metadata and listed in the SDK entrypoint inventory, matching the extension-boundary rule to use plugin SDK imports. (package.json:1420, bdd365a34882)
  • Runtime caller path: Matrix creates the thread context resolver in the monitor handler and passes threadStarterBody into supplemental channel context for the agent prompt path. (extensions/matrix/src/matrix/monitor/handler.ts:544, bdd365a34882)
  • PR diff implements the narrow fix: The PR imports sliceUtf16Safe, uses it at the single Matrix truncation site, and adds a regression test asserting the emoji-boundary output has no surrogate code units. (extensions/matrix/src/matrix/monitor/thread-context.ts:18, 15beecb66b6a)

Likely related people:

  • Gustavo Madeira Santana: Blame on the prior Matrix-thread-context history shows the raw truncation helper and slice line originate in the Matrix plugin migration branch commit. (role: introduced current behavior; confidence: medium; commits: 94693f7ff036; files: extensions/matrix/src/matrix/monitor/thread-context.ts)
  • jacobtomlinson: Recent history on the same Matrix thread-context file includes a fix filtering fetched room context by sender allowlist. (role: recent Matrix context contributor; confidence: medium; commits: 8a563d603b70; files: extensions/matrix/src/matrix/monitor/thread-context.ts)
  • alberthild: Recent history on the same file includes reply-context body and sender handling, which is adjacent to the thread-starter summary path. (role: adjacent Matrix context contributor; confidence: medium; commits: c7fbd518909a; files: extensions/matrix/src/matrix/monitor/thread-context.ts)
  • zhangguiping-xydt: Current-main blame on the touched lines points to the merged current snapshot that carried the Matrix file forward, though that PR was not Matrix-specific. (role: current snapshot carrier; confidence: low; commits: e9f9a68d68c9; files: extensions/matrix/src/matrix/monitor/thread-context.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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 keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@vincentkoc vincentkoc self-assigned this Jun 26, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 26, 2026
@vincentkoc
vincentkoc merged commit 289865b into openclaw:main Jun 26, 2026
127 of 133 checks passed
@vincentkoc

Copy link
Copy Markdown
Member

Merged via squash.

wangmiao0668000666 pushed a commit to wangmiao0668000666/openclaw that referenced this pull request Jun 27, 2026
…penclaw#97121)

Matrix thread-starter previews truncated long bodies by raw UTF-16
slice, which could cut an astral character (e.g. emoji) and leave a lone
surrogate, rendering mojibake in the agent's thread context.

Reuse the existing sliceUtf16Safe helper so the cut backs up to a valid
surrogate boundary, preserving the 500-code-unit limit and '...' suffix.
Adds a regression test that fails against the raw-slice implementation.

Salvages the original fix from openclaw#96407 (auto-closed by the active-PR
queue cap). Preserves @ly-wang19's authorship; rebased clean onto main
by @Bartok9.

Co-authored-by: ly-wang19 <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 27, 2026
…penclaw#97121)

Matrix thread-starter previews truncated long bodies by raw UTF-16
slice, which could cut an astral character (e.g. emoji) and leave a lone
surrogate, rendering mojibake in the agent's thread context.

Reuse the existing sliceUtf16Safe helper so the cut backs up to a valid
surrogate boundary, preserving the 500-code-unit limit and '...' suffix.
Adds a regression test that fails against the raw-slice implementation.

Salvages the original fix from openclaw#96407 (auto-closed by the active-PR
queue cap). Preserves @ly-wang19's authorship; rebased clean onto main
by @Bartok9.

Co-authored-by: ly-wang19 <[email protected]>
xydigit-zt pushed a commit to xydigit-zt/xydigit-zt-openclaw that referenced this pull request Jun 28, 2026
…penclaw#97121)

Matrix thread-starter previews truncated long bodies by raw UTF-16
slice, which could cut an astral character (e.g. emoji) and leave a lone
surrogate, rendering mojibake in the agent's thread context.

Reuse the existing sliceUtf16Safe helper so the cut backs up to a valid
surrogate boundary, preserving the 500-code-unit limit and '...' suffix.
Adds a regression test that fails against the raw-slice implementation.

Salvages the original fix from openclaw#96407 (auto-closed by the active-PR
queue cap). Preserves @ly-wang19's authorship; rebased clean onto main
by @Bartok9.

Co-authored-by: ly-wang19 <[email protected]>
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…penclaw#97121)

Matrix thread-starter previews truncated long bodies by raw UTF-16
slice, which could cut an astral character (e.g. emoji) and leave a lone
surrogate, rendering mojibake in the agent's thread context.

Reuse the existing sliceUtf16Safe helper so the cut backs up to a valid
surrogate boundary, preserving the 500-code-unit limit and '...' suffix.
Adds a regression test that fails against the raw-slice implementation.

Salvages the original fix from openclaw#96407 (auto-closed by the active-PR
queue cap). Preserves @ly-wang19's authorship; rebased clean onto main
by @Bartok9.

Co-authored-by: ly-wang19 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: matrix Channel integration: matrix proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: XS status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants