Skip to content

fix(googlechat): truncate approval card text on UTF-16 boundary#96573

Merged
vincentkoc merged 1 commit into
openclaw:mainfrom
WeeLi-009:fix/googlechat-truncate-surrogate
Jun 27, 2026
Merged

fix(googlechat): truncate approval card text on UTF-16 boundary#96573
vincentkoc merged 1 commit into
openclaw:mainfrom
WeeLi-009:fix/googlechat-truncate-surrogate

Conversation

@WeeLi-009

@WeeLi-009 WeeLi-009 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary: Google Chat approval-card text was truncated with a raw UTF-16 slice, so an emoji near the limit became a lone surrogate in the cardsV2 API payload. This uses truncateUtf16Safe to truncate on a code-point boundary.

What Problem This Solves

Google Chat approval cards truncated long text by UTF-16 code unit length. When the truncation boundary landed inside an astral character, such as a command containing 1796 a characters followed by 😀, the old raw .slice() behavior kept only the high surrogate (0xd83d) before appending ....

That produced ill-formed UTF-16 in the Google Chat card text. The terminal proof below reproduces the old boundary with raw .slice() and shows encodeURIComponent rejecting the result with URIError: URI malformed.

Fix

extensions/googlechat/src/approval-handler.runtime.ts now routes Google Chat approval text truncation through the shared truncateUtf16Safe helper before appending the suffix.

The helper clamps and floors the requested length, slices through the shared UTF-16-safe path, and adjusts slice edges away from dangling high/low surrogate halves. Google Chat still escapes plain command/card text after truncation, so the final card text remains both HTML-escaped and UTF-16 well formed.

Evidence

The demo imports the real Google Chat approval runtime from extensions/googlechat/src/approval-handler.runtime.ts and calls googleChatApprovalNativeRuntime.presentation.buildPendingPayload with an emoji/surrogate-boundary command. The BEFORE section uses the old raw .slice() shape for comparison; the AFTER section reads the actual fixed Google Chat card text.

Command:

node --import tsx demo.mts

Output:

input.length 1898
emoji boundary codePointAt(1796) 0x1f600
BEFORE raw slice length 1800
BEFORE codePointAt(1796) 0xd83d
BEFORE lone-surrogate-hex 0xd83d
BEFORE lone-surrogate true
BEFORE encodeURIComponent URIError: URI malformed
AFTER source googleChatApprovalNativeRuntime.presentation.buildPendingPayload
AFTER fixed card text length 1799
AFTER tail "aaaaa..."
AFTER codePointAt(1796) 0x2e
AFTER lone-surrogate-hex none
AFTER lone-surrogate false
AFTER encodeURIComponent aaaaaaaaa...

The fixed runtime output has lone-surrogate false, no dangling surrogate hex value, and survives URI encoding, so the Google Chat approval card text is well formed at the emoji truncation boundary.

Tests

Vitest regression coverage in extensions/googlechat/src/approval-handler.runtime.test.ts covers the red/green behavior for Google Chat approval card truncation:

  • keeps truncated pending command card text UTF-16 well formed covers the exact boundary where raw truncation would leave \ud83d.
  • preserves a complete astral character when it fits before the truncation suffix covers the adjacent case where the complete emoji should be retained before ....

@openclaw-barnacle openclaw-barnacle Bot added channel: googlechat Channel integration: googlechat size: S triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jun 24, 2026
@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 26, 2026, 2:08 AM ET / 06:08 UTC.

Summary
The PR updates Google Chat approval-card truncation to use the shared UTF-16-safe helper and adds regression tests for emoji/surrogate boundaries.

PR surface: Source +1, Tests +64. Total +65 across 2 files.

Reproducibility: yes. Source inspection shows current main and v2026.6.10 truncate Google Chat card text with raw slice, and the PR body includes terminal output demonstrating the old surrogate split and after-fix well-formed output.

Review metrics: none identified.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
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.

Mantis proof suggestion
A visible Google Chat card proof would add useful transport-level confidence for the rendered approval card, although terminal proof already covers the string boundary. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis visual task: verify a Google Chat approval card with an emoji at the truncation boundary renders without malformed text and keeps approval buttons usable.

Next step before merge

  • No automated repair target is needed; a maintainer should do the normal final review and decide whether optional live Google Chat visual proof is worth requesting.

Security
Cleared: No concrete security or supply-chain concern was found; the diff imports an existing SDK helper and adds focused tests without touching secrets, dependencies, workflows, or publishing paths.

Review details

Best possible solution:

Land the focused Google Chat truncation fix after normal maintainer review, keeping the change at the existing card-text formatting boundary and shared SDK helper.

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

Yes. Source inspection shows current main and v2026.6.10 truncate Google Chat card text with raw slice, and the PR body includes terminal output demonstrating the old surrogate split and after-fix well-formed output.

Is this the best way to solve the issue?

Yes. Reusing truncateUtf16Safe in the existing Google Chat truncateText helper is the narrowest maintainable fix because it preserves the current length budget, suffix, escaping flow, and plugin SDK boundary.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: This fixes a channel-specific approval-card correctness bug with limited blast radius and clear regression coverage.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes terminal output from a demo importing the real Google Chat approval runtime and showing after-fix card text has no lone surrogate and survives URI encoding.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes terminal output from a demo importing the real Google Chat approval runtime and showing after-fix card text has no lone surrogate and survives URI encoding.
Evidence reviewed

PR surface:

Source +1, Tests +64. Total +65 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 2 1 +1
Tests 1 64 0 +64
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 66 1 +65

What I checked:

Likely related people:

  • kevinslin: Merged PR metadata shows this person authored and merged the Google Chat native approval-card runtime that contains the affected truncation helper. (role: introduced behavior; confidence: high; commits: e187c3b7068d, 5923f2af4620; files: extensions/googlechat/src/approval-handler.runtime.ts, extensions/googlechat/src/approval-handler.runtime.test.ts)
  • Peter Steinberger: Git history shows this person introduced the shared UTF-16-safe truncation utility that the PR reuses. (role: adjacent utility owner; confidence: medium; commits: 63f5fa47deb6; files: src/utils.ts)
  • zhang-guiping: Current blame for the Google Chat truncate helper and shared UTF-16 helper points to a broad recent commit carrying these files into current main; the commit scope makes ownership confidence low. (role: recent area contributor; confidence: low; commits: bead84f0ee6e; files: extensions/googlechat/src/approval-handler.runtime.ts, extensions/googlechat/src/approval-handler.runtime.test.ts, src/utils.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.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 24, 2026
truncateText sliced the approval card text paragraph with String.slice,
which can cut through an astral character's surrogate pair (e.g. an emoji
straddling the 1797-char limit), leaving a lone surrogate in the card
text sent to Google Chat.

Use truncateUtf16Safe from the plugin SDK so truncation never splits a
surrogate pair, keeping the '...' suffix and the existing length budget.

Adds tests asserting the truncated Command card text stays UTF-16 well
formed and that an astral character is preserved when it fits.
@WeeLi-009
WeeLi-009 force-pushed the fix/googlechat-truncate-surrogate branch from 7ac6010 to acb2748 Compare June 25, 2026 04:17
@clawsweeper clawsweeper Bot added 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. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 25, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jun 25, 2026
@vincentkoc
vincentkoc merged commit 2e881ab into openclaw:main Jun 27, 2026
119 of 128 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 28, 2026
…claw#96573)

truncateText sliced the approval card text paragraph with String.slice,
which can cut through an astral character's surrogate pair (e.g. an emoji
straddling the 1797-char limit), leaving a lone surrogate in the card
text sent to Google Chat.

Use truncateUtf16Safe from the plugin SDK so truncation never splits a
surrogate pair, keeping the '...' suffix and the existing length budget.

Adds tests asserting the truncated Command card text stays UTF-16 well
formed and that an astral character is preserved when it fits.
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…claw#96573)

truncateText sliced the approval card text paragraph with String.slice,
which can cut through an astral character's surrogate pair (e.g. an emoji
straddling the 1797-char limit), leaving a lone surrogate in the card
text sent to Google Chat.

Use truncateUtf16Safe from the plugin SDK so truncation never splits a
surrogate pair, keeping the '...' suffix and the existing length budget.

Adds tests asserting the truncated Command card text stays UTF-16 well
formed and that an astral character is preserved when it fits.
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
…claw#96573)

truncateText sliced the approval card text paragraph with String.slice,
which can cut through an astral character's surrogate pair (e.g. an emoji
straddling the 1797-char limit), leaving a lone surrogate in the card
text sent to Google Chat.

Use truncateUtf16Safe from the plugin SDK so truncation never splits a
surrogate pair, keeping the '...' suffix and the existing length budget.

Adds tests asserting the truncated Command card text stays UTF-16 well
formed and that an astral character is preserved when it fits.
Rorqualx pushed a commit to Rorqualx/cortex that referenced this pull request Jul 2, 2026
…claw#96573)

truncateText sliced the approval card text paragraph with String.slice,
which can cut through an astral character's surrogate pair (e.g. an emoji
straddling the 1797-char limit), leaving a lone surrogate in the card
text sent to Google Chat.

Use truncateUtf16Safe from the plugin SDK so truncation never splits a
surrogate pair, keeping the '...' suffix and the existing length budget.

Adds tests asserting the truncated Command card text stays UTF-16 well
formed and that an astral character is preserved when it fits.

(cherry picked from commit 2e881ab)
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
…claw#96573)

truncateText sliced the approval card text paragraph with String.slice,
which can cut through an astral character's surrogate pair (e.g. an emoji
straddling the 1797-char limit), leaving a lone surrogate in the card
text sent to Google Chat.

Use truncateUtf16Safe from the plugin SDK so truncation never splits a
surrogate pair, keeping the '...' suffix and the existing length budget.

Adds tests asserting the truncated Command card text stays UTF-16 well
formed and that an astral character is preserved when it fits.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: googlechat Channel integration: googlechat P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S 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