fix(subagents): truncate display label on code-point boundaries#96416
Closed
ly-wang19 wants to merge 1 commit into
Closed
fix(subagents): truncate display label on code-point boundaries#96416ly-wang19 wants to merge 1 commit into
ly-wang19 wants to merge 1 commit into
Conversation
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]>
|
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. |
This was referenced Jun 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Problem This Solves
truncateLineinsrc/shared/subagents-format.tstruncates a subagent displaylabel with a raw
value.slice(0, maxLength). UTF-16.slicecuts at a codeunit 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 lonesurrogate, and
"..."is appended after it — producing a label that ends in anunpaired surrogate, which renders as a broken
�glyph (or worse, corruptsdownstream encoding).
Repro —
truncateLine("y".repeat(47) + "\u{1F600}" + "tail", 48)(47
ys, then 😀 occupying UTF-16 units 47-48, thentail; string length 53,max 48):
"yyy…y" + "\uD83D" + "..."— the slice stops at unit 48,keeping the lone high surrogate
\uD83D(the 😀 cut in half) right before theellipsis.
"yyy…y..."— the cut backs off to the code-point boundarybefore the emoji (unit 47), so the emoji is dropped whole and the label never
contains a lone surrogate.
The fix reuses the already-shared
truncateUtf16Safehelper fromsrc/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,
truncateUtf16Safereturns exactly
value.slice(0, maxLength), so non-emoji behavior — includingthe trailing-whitespace trim and the short-string passthrough — is unchanged.
Evidence
Node red-green proof. The script replicates the canonical
sliceUtf16Safe/truncateUtf16Safehelpers and both the buggy and fixedtruncateLinebodies,then asserts the repro input maps to the expected output and that unaffected
inputs are byte-for-byte unchanged:
(In the RED case, walking the buggy output's code units finds an unpaired high
surrogate
\uD83Dimmediately before the2e,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 codeunit, and confirms an emoji that fully fits under the limit is preserved intact.
🤖 Generated with Claude Code