fix(acp): keep background-task summaries UTF-16 safe at truncation boundaries#101976
Conversation
…undaries The bounded task label and progress summary shown for child ACP background runs used raw String.slice, which can split a surrogate pair at the 160/240-char boundaries and surface U+FFFD in requester-facing task status. Part of the UTF-16 safety sweep.
|
Codex review: needs maintainer review before merge. Reviewed July 7, 2026, 10:50 PM ET / 02:50 UTC. Summary PR surface: Source +1, Tests +67. Total +68 across 2 files. Reproducibility: yes. from source inspection: 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 this focused helper swap with the regression tests after exact-head merge prep; broader unrelated raw-slice cleanup should stay in separate surface-specific PRs. Do we have a high-confidence way to reproduce the issue? Yes from source inspection: current main uses raw Is this the best way to solve the issue? Yes; the patch uses the existing shared UTF-16-safe truncation helper already used by sibling ACP/event and runtime display surfaces, preserving the existing limits and normalization behavior. A broader sweep of unrelated raw-slice sites would be a less focused fix for this PR. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 4bdf22444203. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +1, Tests +67. Total +68 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
|
|
Land-ready review complete on exact head
No remaining findings or proof gaps. |
|
Merged via squash.
|
…undaries (openclaw#101976) The bounded task label and progress summary shown for child ACP background runs used raw String.slice, which can split a surrogate pair at the 160/240-char boundaries and surface U+FFFD in requester-facing task status. Part of the UTF-16 safety sweep.
What Problem This Solves
Fixes an issue where operators watching a child ACP background run would see a corrupted replacement character (U+FFFD) at the end of the task label or progress summary when the truncation boundary landed in the middle of an emoji or other astral character. Both requester-facing strings for detached ACP tasks were affected.
Part of the UTF-16 safety sweep (same defect class as the landed
acp/event-mapper,http-error-body,tasks, andcontrol-ui-assetsfixes).Why This Change Was Made
src/acp/control-plane/manager.background-task.tsbounded both summaries with rawString.slice:summarizeBackgroundTaskText()cut atACP_BACKGROUND_TASK_TEXT_MAX_LENGTH - 1(159) andappendBackgroundTaskProgressSummary()atACP_BACKGROUND_TASK_PROGRESS_MAX_LENGTH - 1(239). A surrogate pair astride either index leaves a lone high surrogate before the…marker. Both sites now usetruncateUtf16Safe()from@openclaw/normalization-core/utf16-slice, the same helper the sibling ACPevent-mapper.tsalready uses for its argument previews.This module owns the bounded requester-facing task strings; the PR changes only these two truncation expressions and leaves chunk normalization, terminal-result inference, and the task-registry calls intact.
Non-goals: no change to the length limits themselves, to whitespace normalization, or to any other truncation site in the codebase.
User Impact
ACP background-task labels and progress lines now always end with well-formed text before the ellipsis — no more
�in task status when the summary happens to cut through an emoji. Plain-ASCII truncation output is byte-for-byte unchanged (covered by regression).Evidence
All proof from current head
8c96243892(branched fromupstream/main6765eb0166), Node v24.4.0.Real module behavior — the exported
appendBackgroundTaskProgressSummary()invoked viatsx, before/after:Negative control — with the new regression tests present but the source fix reverted, exactly the two surrogate-boundary tests fail and the plain-ASCII behavior tests still pass (confirming no behavior change outside the boundary):
After fix:
node scripts/run-vitest.mjs src/acp/control-plane/manager.background-task.test.ts— 5 passedpnpm exec oxlint <changed files>— cleanpnpm exec oxfmt --check --threads=1 <changed files>— "All matched files use the correct format"pnpm tsgo— passedpnpm check:changeddelegates to the Blacksmith Testbox remote sandbox, which contributor machines cannot dispatch; the local lanes above cover lint, format, types, and the changed test file.AI-assisted (Claude Code); I have reviewed and understand every line.