fix(gateway): keep command-list field clamping UTF-16 safe#102816
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 9, 2026, 9:19 AM ET / 13:19 UTC. Summary PR surface: Source +1, Tests +27. Total +28 across 2 files. Reproducibility: yes. Current main uses raw 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:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the central clamp change with refreshed exact-head validation so gateway command catalogs remain bounded and well-formed without adding protocol or config surface. Do we have a high-confidence way to reproduce the issue? Yes. Current main uses raw Is this the best way to solve the issue? Yes. The central AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 3fd52514e35f. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +1, Tests +27. Total +28 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
|
1a650b5 to
01f5830
Compare
clampString truncated command names, descriptions, aliases, arg names/descriptions, and choice values/labels with a raw value.slice(0, maxLength). When an emoji (or other astral code point) straddles the clamp limit, the raw slice keeps a dangling high surrogate and emits a lone surrogate over the gateway commands.list protocol result. Route the clamp through the shared truncateUtf16Safe primitive so the boundary code point is dropped whole. Adds a regression test through buildCommandsListResult.
Co-authored-by: MoerAI <[email protected]>
01f5830 to
4c09f0f
Compare
|
Land-ready review complete at
No docs or changelog change is needed: this restores the existing bounded-string contract without changing commands, configuration, or protocol limits. @clawsweeper re-review |
|
🦞👀 Command router queued. I will update this comment with the next step. |
|
Merged via squash.
|
…102816) * fix(gateway): keep command-list field clamping UTF-16 safe clampString truncated command names, descriptions, aliases, arg names/descriptions, and choice values/labels with a raw value.slice(0, maxLength). When an emoji (or other astral code point) straddles the clamp limit, the raw slice keeps a dangling high surrogate and emits a lone surrogate over the gateway commands.list protocol result. Route the clamp through the shared truncateUtf16Safe primitive so the boundary code point is dropped whole. Adds a regression test through buildCommandsListResult. * test(gateway): fold UTF-16 clamp regression into bounds coverage Co-authored-by: MoerAI <[email protected]> --------- Co-authored-by: Peter Steinberger <[email protected]>
What Problem This Solves
The gateway
commands.listresult clamps command names, descriptions, aliases, argument names/descriptions, and choice values/labels to bounded lengths. The clamp used a rawvalue.slice(0, maxLength), so when an emoji or other astral code point straddled the limit, the truncation kept a dangling high surrogate. That lone surrogate is then serialized into the gateway protocol payload delivered to every SDK / Control UI client.Why This Change Was Made
Route the single
clampStringchokepoint through the sharedtruncateUtf16Safeprimitive (@openclaw/normalization-core/utf16-slice), which backs off one code unit when the boundary would split a surrogate pair. Every clamped field (name, description, aliases, args, choices) flows through this one helper, so the fix covers all of them at once. Bounds, ordering, and thelength <= maxLengthguarantee are unchanged; the clamp can only get one code unit shorter in the boundary case, never longer.User Impact
Command metadata delivered over
commands.liststays valid Unicode. An emoji at a clamp boundary is dropped whole instead of surfacing as a broken/replacement character in clients.Evidence
Real behavior at the 2000-unit description boundary (
"d".repeat(1999) + "😀"), using the shippedtruncateUtf16Safe:Before (raw slice): length 2000, last unit
0xd83d,isWellFormed() = false(dangling high surrogate)After (
truncateUtf16Safe): length 1999, last unit0x64,isWellFormed() = trueFolded the regression into the existing bounds test through the real
buildCommandsListResultpath. It asserts the exact safe prefix when an emoji straddlesCOMMAND_DESCRIPTION_MAX_LENGTH.Sanitized AWS Crabbox GREEN:
run_7839d96ceaa3— 52/52 focused tests passed across both gateway projects.Sanitized AWS Crabbox RED control:
run_2558eb9ccc6d— restoring only the pre-fix raw-slice implementation makes the exact-prefix assertion fail in both gateway projects (50 passed, 2 failed).Exact-head hosted CI: all required checks passed, including lint, production/test types, gateway critical quality, and security checks.
Fresh autoreview: no actionable findings;
git diff --checkclean.AI-assisted (Claude Code); I reviewed and understand every line.