Skip to content

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

Closed
Bartok9 wants to merge 3 commits into
openclaw:mainfrom
Bartok9:salvage/96416-subagents-codepoint
Closed

fix(subagents): truncate display label on code-point boundaries#96959
Bartok9 wants to merge 3 commits into
openclaw:mainfrom
Bartok9:salvage/96416-subagents-codepoint

Conversation

@Bartok9

@Bartok9 Bartok9 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Salvages #96416 by @ly-wang19 onto current main — clean single-commit rebase, original authorship preserved. The original was auto-closed by openclaw-barnacle for the author's >20-active-PR cap, not on merits; the bug is still live on main.

The subagent display-label truncation (src/shared/subagents-format.ts) cuts the label at a raw UTF-16 code-unit offset, so a label ending near the limit on an emoji/astral character produces a lone surrogate before the ... — mojibake in the rendered subagent label.

Why This Change Was Made

Symptom — A subagent label longer than maxLength whose boundary falls inside a surrogate pair renders a broken glyph before the ellipsis.

Root cause — Line 32: return \${value.slice(0, maxLength).trimEnd()}...`;. slice(0, maxLength)is a code-*unit* cut; ifmaxLength` lands between a high/low surrogate, the pair is severed.

Fix + why this level — Back the cut off to a code-point boundary before slicing (drop a trailing lone high surrogate). The truncation function is the only place that produces the boundary, so it's the correct layer.

Scope / risk — Touches only the label-truncation path. Short labels and BMP-only labels are unaffected (no-op when already aligned). The ... suffix and trimEnd() behavior are preserved.

User Impact

Subagent labels that end at an astral character near the truncation limit now remain valid Unicode instead of rendering a broken glyph.

Evidence

  • Regression test (new): src/shared/subagents-format.test.ts — asserts no lone surrogate at the cut; fails without the fix.
  • Focused test on this branch: node scripts/run-vitest.mjs src/shared/subagents-format.test.ts — 6/6 passed.
  • Without the fix: the regression test fails against current main source (1 failed, 5 passed).
  • git diff --check origin/main...HEAD is clean.
  • Fresh autoreview found no actionable findings.

Provenance and Credit

Credit: Salvage of #96416 by @ly-wang19 — rebased onto current main. Original closed by the >20-active-PR queue cap, not on merits. No code changes from @ly-wang19's original.

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 triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jun 26, 2026
@clawsweeper

clawsweeper Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed June 26, 2026, 7:05 PM ET / 23:05 UTC.

Summary
The latest live branch contains the subagent truncateLine UTF-16-safe helper/test change plus broad unrelated changes across changelog, docs/i18n, scripts, plugin, memory, and UI surfaces.

PR surface: Source +4, Tests +18. Total +22 across 2 files.

Reproducibility: yes. Source inspection shows current main uses raw UTF-16 slicing in truncateLine, and the PR proof gives a concrete surrogate-boundary input that leaves a lone high surrogate before the ellipsis on the old path.

Review metrics: 1 noteworthy metric.

  • Live head scope drift: 239 files at e5199989208d963b7df164c06a3fc75a12e675ff versus 2 intended subagent files. The live branch has grown far beyond the stated bug fix, so maintainers need the scope narrowed before normal review or merge.

Root-cause cluster
Relationship: canonical
Canonical: #96959
Summary: This PR remains the active candidate for the subagent display-label truncation bug, but its latest head needs branch-scope repair; the predecessor is closed unmerged and the sibling PR covers a different display surface.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🦞 diamond lobster
Patch quality: 🧂 unranked krab
Result: blocked by patch quality or review findings.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Narrow or recreate the branch so only src/shared/subagents-format.ts and src/shared/subagents-format.test.ts change.
  • Rerun the focused subagent formatter test and a diff check after the branch is narrowed.

Risk before merge

  • [P1] The latest live head is a 239-file branch, not the focused two-file subagent fix, so merging it would bring unrelated changelog, docs/i18n, scripts, plugin, memory, and UI changes into a narrow bug-fix PR.
  • [P1] The live diff changes plain-gh executable resolution in scripts/lib/plain-gh.mjs and scripts/lib/plain-gh.sh, which is a tooling trust path unrelated to the display-label bug and should be reviewed separately or removed before merge.

Maintainer options:

  1. Narrow the branch before merge (recommended)
    Recreate or rebase the branch from current main with only the two subagent formatter files, dropping the unrelated e5199989208d963b7df164c06a3fc75a12e675ff repository snapshot.
  2. Split unrelated changes into their own review
    If any of the script, i18n, docs, memory, plugin, or UI changes are intentional, move them to separate PRs with their own purpose, proof, and owner review.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Recreate the PR branch from current main with only the intended `src/shared/subagents-format.ts` and `src/shared/subagents-format.test.ts` changes; drop the broad unrelated `e5199989208d963b7df164c06a3fc75a12e675ff` snapshot, do not touch `CHANGELOG.md`, docs/i18n, scripts, plugin SDK baselines, UI, extensions, or memory files, then run `node scripts/run-vitest.mjs src/shared/subagents-format.test.ts` and `git diff --check`.

Next step before merge

  • [P2] A repair worker can mechanically recreate the branch as the focused two-file subagent formatter fix and drop the unrelated live-head snapshot.

Security
Needs attention: The intended two-file string fix is not security-sensitive, but the current live branch also changes unrelated tooling executable-resolution code that must be removed or separately reviewed.

Review findings

  • [P1] Drop the unrelated repository snapshot — CHANGELOG.md:4-10
Review details

Best possible solution:

Keep the subagent helper/test fix, but recreate or rebase the PR so it contains only src/shared/subagents-format.ts and src/shared/subagents-format.test.ts on current main.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection shows current main uses raw UTF-16 slicing in truncateLine, and the PR proof gives a concrete surrogate-boundary input that leaves a lone high surrogate before the ellipsis on the old path.

Is this the best way to solve the issue?

No for the live branch as submitted now. The two-file helper reuse is the best fix for the bug, but the current 239-file branch must be narrowed before it is a safe merge vehicle.

Full review comments:

  • [P1] Drop the unrelated repository snapshot — CHANGELOG.md:4-10
    The latest live head is no longer the focused two-file subagent fix: comparing live main a82902c725b215294318ac16b50b2a40d0d23aa7 to e5199989208d963b7df164c06a3fc75a12e675ff shows 239 changed files (+19226/-890), including changelog, docs/i18n, scripts, plugin SDK, memory, and UI changes. Please recreate or rebase the branch so this PR contains only the subagent formatter and test changes; otherwise this cannot be safely reviewed or merged as a narrow Unicode truncation fix.
    Confidence: 0.94

Overall correctness: patch is incorrect
Overall confidence: 0.91

AGENTS.md: found and applied where relevant.

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

Label changes

Label changes:

  • add merge-risk: 🚨 compatibility: The current live head changes broad docs, UI, plugin, memory, generated baseline, and script surfaces unrelated to the subagent formatter, which could affect existing behavior outside the intended fix.
  • add merge-risk: 🚨 security-boundary: The live diff includes unrelated changes to plain-gh executable resolution, a tooling code-execution path that should not ride along with a display-label truncation PR.
  • add rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🦞 diamond lobster and patch quality is 🧂 unranked krab.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The contributor provided terminal-style before/after output for the intended truncateLine behavior, including the surrogate-straddle and emoji-fits cases; it does not make the broadened live branch merge-ready.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🧂 unranked krab, so this older rating label is no longer current.
  • remove status: 👀 ready for maintainer look: Current PR status label is status: ⏳ waiting on author.

Label justifications:

  • P2: The intended fix is a normal-priority display correctness bug with limited blast radius, but the live branch needs cleanup before merge.
  • merge-risk: 🚨 compatibility: The current live head changes broad docs, UI, plugin, memory, generated baseline, and script surfaces unrelated to the subagent formatter, which could affect existing behavior outside the intended fix.
  • merge-risk: 🚨 security-boundary: The live diff includes unrelated changes to plain-gh executable resolution, a tooling code-execution path that should not ride along with a display-label truncation PR.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🦞 diamond lobster and patch quality is 🧂 unranked krab.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The contributor provided terminal-style before/after output for the intended truncateLine behavior, including the surrogate-straddle and emoji-fits cases; it does not make the broadened live branch merge-ready.
  • proof: sufficient: Contributor real behavior proof is sufficient. The contributor provided terminal-style before/after output for the intended truncateLine behavior, including the surrogate-straddle and emoji-fits cases; it does not make the broadened live branch merge-ready.
Evidence reviewed

PR surface:

Source +4, Tests +18. Total +22 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 5 1 +4
Tests 1 18 0 +18
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 23 1 +22

Security concerns:

  • [medium] Unrelated tool-resolution code rides along — scripts/lib/plain-gh.mjs:6
    The latest head changes plain-gh binary resolution in a PR whose stated purpose is subagent label truncation. That script/tooling trust path should be removed from this branch or reviewed in its own PR with matching proof.
    Confidence: 0.82

Acceptance criteria:

  • [P1] node scripts/run-vitest.mjs src/shared/subagents-format.test.ts.
  • [P1] git diff --check.
  • [P1] Confirm the final PR diff only includes src/shared/subagents-format.ts and src/shared/subagents-format.test.ts.

What I checked:

  • Root policy read: The repository root policy was read fully, and its PR review guidance affected the review by requiring current-main source checks, scoped policy checks, caller/callee inspection, related PR context, and history provenance. (AGENTS.md:1, aca905cce50a)
  • Scoped policy check: No scoped AGENTS.md exists under src/shared; src/agents/AGENTS.md was read because subagent-list is the main caller path. (src/agents/AGENTS.md:1, aca905cce50a)
  • Current main still has the raw slice: The checked-out main implementation still returns value.slice(0, maxLength).trimEnd() before appending ..., so it can split a surrogate pair at the display boundary. (src/shared/subagents-format.ts:32, aca905cce50a)
  • Live GitHub main also still has the raw slice: The live GitHub main branch at a82902c725b215294318ac16b50b2a40d0d23aa7 still shows the same raw slice truncation path. (src/shared/subagents-format.ts:32, a82902c725b2)
  • Caller path: buildSubagentList formats both subagent labels and task text through truncateLine, making the shared formatter the right fix boundary. (src/agents/subagent-list.ts:267, aca905cce50a)
  • Existing helper contract: truncateUtf16Safe delegates to sliceUtf16Safe, which adjusts slice boundaries away from dangling surrogate halves at either edge. (src/utils.ts:81, aca905cce50a)

Likely related people:

  • giodl73-repo: Git blame and PR metadata tie the current truncateLine, UTF-16 helper, and subagent-list usage to the merged Doctor: add lint --all work from Doctor: add lint --all #96471. (role: introduced current formatter path; confidence: high; commits: 4fc504d321b6; files: src/shared/subagents-format.ts, src/shared/subagents-format.test.ts, src/agents/subagent-list.ts)
  • vincentkoc: The latest live PR head e5199989208d963b7df164c06a3fc75a12e675ff, which introduced the broad 239-file branch scope drift, is authored by vincentkoc and committed via web-flow. (role: current branch update author; confidence: high; commits: e5199989208d; files: CHANGELOG.md, scripts/lib/plain-gh.mjs, src/shared/subagents-format.ts)
  • ly-wang19: The closed unmerged predecessor fix(subagents): truncate display label on code-point boundaries #96416 proposed the same subagent truncateLine repair and was closed for queue policy rather than a technical rejection. (role: original fix proposer; confidence: medium; commits: 70e271681a53; files: src/shared/subagents-format.ts, src/shared/subagents-format.test.ts)
  • steipete: Recent history on src/agents/subagent-list.ts includes earlier subagent command/import work by Peter Steinberger, making this a useful adjacent routing signal for the display path. (role: adjacent subagent area contributor; confidence: medium; commits: 7c160f240205, a1eb677241e9; files: src/agents/subagent-list.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 rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jun 26, 2026
@Bartok9

Bartok9 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Real behavior proof — before/after

Host: macOS (Darwin 25.4.0, arm64), node v25.5.0, repo source via its own tsx. Exercising the patched truncateLine directly. Input: "y"×47 + 😀 (U+1F600, surrogate pair at units 47-48) + "tail", truncated at maxLength=48 — so the cut lands mid-pair.

Before (upstream/main: value.slice(0, maxLength))

BEFORE naive slice(0,48): lone surrogate? true  (head ends with charCode d83d)

The old code keeps the emoji's lone high surrogate (U+D83D) right before the ....

After (this branch — truncateUtf16Safe)

AFTER truncateLine(value,48): "yyyy…(47 y)…..."
  === "y"*47 + "..."  : true     (emoji dropped whole at the cut)
  lone surrogate?     : false
emoji-fits case: "y"*46 + 😀 + "..."  -> true     (emoji below limit preserved intact)
short: true | trim: "trim me   " -> "trim me..." : true     (plain behavior preserved)
RESULT: PASS

These match the added subagents-format.test.ts assertions exactly (surrogate-straddle drop, emoji-fits preservation, plain short/trim cases). tsc --noEmit is clean on the changed files. (build-artifacts + QA Smoke already green on this PR.)

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@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. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 26, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jun 26, 2026
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: telegram Channel integration: telegram app: web-ui App: web-ui gateway Gateway runtime extensions: memory-core Extension: memory-core cli CLI command changes scripts Repository scripts commands Command implementations agents Agent runtime and tooling channel: feishu Channel integration: feishu extensions: openai extensions: minimax extensions: fal labels Jun 26, 2026
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed 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. labels Jun 26, 2026
@vincentkoc vincentkoc added the dirty Maintainer-applied auto-close for dirty/unrelated PR branches. label Jun 26, 2026
@openclaw-barnacle

Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

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

Labels

agents Agent runtime and tooling app: web-ui App: web-ui channel: feishu Channel integration: feishu channel: telegram Channel integration: telegram cli CLI command changes commands Command implementations dirty Maintainer-applied auto-close for dirty/unrelated PR branches. docs Improvements or additions to documentation extensions: deepinfra extensions: fal extensions: google extensions: litellm extensions: memory-core Extension: memory-core extensions: minimax extensions: openai extensions: openrouter extensions: xai gateway Gateway runtime P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. scripts Repository scripts size: XL status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants