Skip to content

fix(gateway): keep command-list field clamping UTF-16 safe#102816

Merged
steipete merged 2 commits into
openclaw:mainfrom
MoerAI:fix/commands-list-utf16-safe
Jul 9, 2026
Merged

fix(gateway): keep command-list field clamping UTF-16 safe#102816
steipete merged 2 commits into
openclaw:mainfrom
MoerAI:fix/commands-list-utf16-safe

Conversation

@MoerAI

@MoerAI MoerAI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

The gateway commands.list result clamps command names, descriptions, aliases, argument names/descriptions, and choice values/labels to bounded lengths. The clamp used a raw value.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 clampString chokepoint through the shared truncateUtf16Safe primitive (@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 the length <= maxLength guarantee are unchanged; the clamp can only get one code unit shorter in the boundary case, never longer.

User Impact

Command metadata delivered over commands.list stays 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 shipped truncateUtf16Safe:

  • Before (raw slice): length 2000, last unit 0xd83d, isWellFormed() = false (dangling high surrogate)

  • After (truncateUtf16Safe): length 1999, last unit 0x64, isWellFormed() = true

  • Folded the regression into the existing bounds test through the real buildCommandsListResult path. It asserts the exact safe prefix when an emoji straddles COMMAND_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 --check clean.

AI-assisted (Claude Code); I reviewed and understand every line.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: XS labels Jul 9, 2026
@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 9, 2026, 9:19 AM ET / 13:19 UTC.

Summary
The PR routes gateway command-list string clamping through truncateUtf16Safe and adds a regression test for a surrogate-pair boundary in command descriptions.

PR surface: Source +1, Tests +27. Total +28 across 2 files.

Reproducibility: yes. Current main uses raw slice(0, maxLength) at the command-list clamp, and the PR body shows the boundary case where a 2,000-unit description ending in an emoji leaves a dangling high surrogate before the fix.

Review metrics: none identified.

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

  • Refresh the branch against current main or rely on merge-queue exact-head checks before landing.

Risk before merge

  • [P1] The PR branch is behind current main, so maintainers should rely on refreshed exact-head or merge-ref validation before landing.

Maintainer options:

  1. Decide the mitigation before merge
    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.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • No ClawSweeper repair lane is needed because the patch has no blocking findings; the next action is normal maintainer review with refreshed merge validation.

Security
Cleared: The diff imports an existing internal normalization helper and adds a test; it does not touch dependencies, secrets, workflows, packaging, or code-execution surfaces.

Review details

Best 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 slice(0, maxLength) at the command-list clamp, and the PR body shows the boundary case where a 2,000-unit description ending in an emoji leaves a dangling high surrogate before the fix.

Is this the best way to solve the issue?

Yes. The central clampString helper is the narrowest maintainable fix because all bounded commands.list string fields pass through it, and the existing truncateUtf16Safe helper is already the repository's UTF-16-safe truncation primitive.

AGENTS.md: found and applied where relevant.

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

Label changes

Label changes:

  • add P2: This is a focused gateway protocol bug fix for malformed command metadata with limited blast radius.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes copied before/after boundary output plus red/green regression-test evidence through buildCommandsListResult, which is sufficient for this pure serialization fix.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🦞 diamond lobster.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes copied before/after boundary output plus red/green regression-test evidence through buildCommandsListResult, which is sufficient for this pure serialization fix.

Label justifications:

  • P2: This is a focused gateway protocol bug fix for malformed command metadata with limited blast radius.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes copied before/after boundary output plus red/green regression-test evidence through buildCommandsListResult, which is sufficient for this pure serialization fix.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes copied before/after boundary output plus red/green regression-test evidence through buildCommandsListResult, which is sufficient for this pure serialization fix.
Evidence reviewed

PR surface:

Source +1, Tests +27. Total +28 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 2 1 +1
Tests 1 27 0 +27
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 29 1 +28

What I checked:

Likely related people:

  • samzong: Authored the merged commands.list RPC method work in [Feat] Gateway: add commands.list RPC method #62656, which established the gateway command catalog surface. (role: introduced behavior; confidence: high; commits: 723dec043214; files: src/gateway/server-methods/commands.ts, src/gateway/server-methods/commands.test.ts)
  • frankekn: Merged the original command-list PR and authored the follow-up metadata preservation fix in fix: preserve commands.list metadata #64147. (role: early feature maintainer; confidence: high; commits: 723dec043214, 360955a7c8ef; files: src/gateway/server-methods/commands.ts, src/gateway/server-methods/commands.test.ts)
  • BunsDev: Authored the Control UI runtime slash-command refresh work in Control UI: refresh slash commands from runtime command list #65620, the main client consumer of commands.list metadata. (role: adjacent UI owner; confidence: medium; commits: d0c83777fb58; files: ui/src/lib/chat/commands.ts, ui/src/pages/chat/chat-commands.ts)
  • velanir-ai-manager: Current blame for the extracted commands-list-result.ts, command schema, tests, and normalization helper points to the recent squash from fix(msteams): surface quoted message body in Teams quote replies #101856. (role: recent area contributor; confidence: medium; commits: e84a0dde175e; files: src/gateway/server-methods/commands-list-result.ts, packages/gateway-protocol/src/schema/commands.ts, packages/normalization-core/src/utf16-slice.ts)
  • steipete: Merged the recent squash that currently owns the extracted gateway command-list files and normalization helper in main history. (role: recent merger; confidence: medium; commits: e84a0dde175e; files: src/gateway/server-methods/commands-list-result.ts, packages/normalization-core/src/utf16-slice.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 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. P2 Normal backlog priority with limited blast radius. labels Jul 9, 2026
@steipete steipete self-assigned this Jul 9, 2026
@steipete
steipete force-pushed the fix/commands-list-utf16-safe branch from 1a650b5 to 01f5830 Compare July 9, 2026 14:02
MoerAI and others added 2 commits July 9, 2026 15:14
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.
@steipete
steipete force-pushed the fix/commands-list-utf16-safe branch from 01f5830 to 4c09f0f Compare July 9, 2026 14:14
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Land-ready review complete at 4c09f0fc255e658ff79167d8e3d534cfc29c61fa.

  • Best-fix review: the gateway result builder is the correct owner. Its single clampString path covers names, descriptions, aliases, arguments, and choices for commands.list plus startup metadata. Client-only or per-field repairs would leave sibling consumers exposed or duplicate policy.
  • Refactor: folded the standalone regression into the existing bounds fixture. Final diff is 2 files, +5/-2, with a stronger exact-prefix assertion.
  • RED control: sanitized AWS run_2558eb9ccc6d restores only the old raw-slice implementation; the new assertion fails in both gateway projects (2 failed, 50 passed).
  • GREEN proof: sanitized AWS run_7839d96ceaa3 passes the focused test in both gateway projects (52/52).
  • Hosted proof: exact-head CI run 29024648570 passed all required jobs, including lint, build, production/test types, gateway critical quality, and security.
  • Fresh autoreview on the rebased head: correct, confidence 0.99, no actionable findings. git diff --check is clean.
  • Both rebased commits are locally signature-valid and GitHub-verified; contributor authorship/co-authorship is preserved.

No docs or changelog change is needed: this restores the existing bounded-string contract without changing commands, configuration, or protocol limits.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

@steipete
steipete merged commit fe18ced into openclaw:main Jul 9, 2026
103 checks passed
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

@MoerAI
MoerAI deleted the fix/commands-list-utf16-safe branch July 10, 2026 07:15
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 10, 2026
…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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime 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: 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.

2 participants