Skip to content

fix(subagents): truncate display label on code-point boundaries#96416

Closed
ly-wang19 wants to merge 1 commit into
openclaw:mainfrom
ly-wang19:fix/subagents-truncate-line-surrogate-safe
Closed

fix(subagents): truncate display label on code-point boundaries#96416
ly-wang19 wants to merge 1 commit into
openclaw:mainfrom
ly-wang19:fix/subagents-truncate-line-surrogate-safe

Conversation

@ly-wang19

Copy link
Copy Markdown
Contributor

What Problem This Solves

truncateLine in src/shared/subagents-format.ts truncates a subagent display
label with a raw value.slice(0, maxLength). UTF-16 .slice cuts at a code
unit boundary, so when an emoji (a surrogate pair) straddles the limit the
slice keeps only the high surrogate half. trimEnd() is a no-op on a lone
surrogate, and "..." is appended after it — producing a label that ends in an
unpaired surrogate, which renders as a broken glyph (or worse, corrupts
downstream encoding).

Repro — truncateLine("y".repeat(47) + "\u{1F600}" + "tail", 48)
(47 ys, then 😀 occupying UTF-16 units 47-48, then tail; string length 53,
max 48):

  • Before (wrong): "yyy…y" + "\uD83D" + "..." — the slice stops at unit 48,
    keeping the lone high surrogate \uD83D (the 😀 cut in half) right before the
    ellipsis.
  • After (fixed): "yyy…y..." — the cut backs off to the code-point boundary
    before the emoji (unit 47), so the emoji is dropped whole and the label never
    contains a lone surrogate.

The fix reuses the already-shared truncateUtf16Safe helper from src/utils.ts
(the same helper used by tasks/task-status.ts, the embedded-agent tools, etc.).
For any input without a surrogate pair on the boundary, truncateUtf16Safe
returns exactly value.slice(0, maxLength), so non-emoji behavior — including
the trailing-whitespace trim and the short-string passthrough — is unchanged.

Evidence

Node red-green proof. The script replicates the canonical sliceUtf16Safe /
truncateUtf16Safe helpers and both the buggy and fixed truncateLine bodies,
then asserts the repro input maps to the expected output and that unaffected
inputs are byte-for-byte unchanged:

reproInput length: 53 (expected 53)
BEFORE codes: 2e,2e,2e
AFTER  : "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy..."
PASS: RED: buggy output contains a lone surrogate
PASS: RED: buggy output != expected
PASS: GREEN: fixed output === expectedOutput got="yyy…yyy..."
PASS: GREEN: fixed output has no lone surrogate
PASS: unchanged: short string passthrough
PASS: unchanged: trailing-whitespace trim case
PASS: unchanged: ascii truncation matches buggy (no surrogates) fixed="abcd..."
PASS: preserved: emoji below limit kept whole got="yyy…yy😀..."

ALL CHECKS PASSED

(In the RED case, walking the buggy output's code units finds an unpaired high
surrogate \uD83D immediately before the 2e,2e,2e = ... tail.)

A matching unit test is added in src/shared/subagents-format.test.ts
("never cuts a surrogate pair in half when truncating"): it asserts the repro
maps to "y".repeat(47) + "...", scans the result for any lone surrogate code
unit, and confirms an emoji that fully fits under the limit is preserved intact.

🤖 Generated with Claude Code

truncateLine used a raw value.slice(0, maxLength), which can split a
surrogate pair when an emoji straddles the limit, leaving a lone high
surrogate before the appended "...". Use the shared truncateUtf16Safe
helper so the half-cut code point is dropped whole. No change for
non-surrogate inputs.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@openclaw-barnacle openclaw-barnacle Bot added size: XS r: too-many-prs Auto-close: author has more than twenty active PRs. labels Jun 24, 2026
@openclaw-barnacle

Copy link
Copy Markdown

Closing this PR because the author has more than 20 active PRs in this repo. Please reduce the active PR queue and reopen or resubmit once it is back under the limit. You can close your own PRs to get back under the limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

r: too-many-prs Auto-close: author has more than twenty active PRs. size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant