fix(googlechat): truncate approval card text on UTF-16 boundary#96573
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 26, 2026, 2:08 AM ET / 06:08 UTC. Summary 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 follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Mantis proof suggestion Next step before merge
Security Review detailsBest 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 changesLabel justifications:
Evidence reviewedPR surface: Source +1, Tests +64. Total +65 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
|
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.
7ac6010 to
acb2748
Compare
…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.
…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.
…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.
…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)
…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.
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
cardsV2API payload. This usestruncateUtf16Safeto 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
1796acharacters 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 showsencodeURIComponentrejecting the result withURIError: URI malformed.Fix
extensions/googlechat/src/approval-handler.runtime.tsnow routes Google Chat approval text truncation through the sharedtruncateUtf16Safehelper 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.tsand callsgoogleChatApprovalNativeRuntime.presentation.buildPendingPayloadwith an emoji/surrogate-boundary command. TheBEFOREsection uses the old raw.slice()shape for comparison; theAFTERsection reads the actual fixed Google Chat card text.Command:
Output:
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.tscovers the red/green behavior for Google Chat approval card truncation:keeps truncated pending command card text UTF-16 well formedcovers the exact boundary where raw truncation would leave\ud83d.preserves a complete astral character when it fits before the truncation suffixcovers the adjacent case where the complete emoji should be retained before....