fix(session-cost-usage): keep emoji / surrogate pairs intact during content truncation#101517
Conversation
…ontent truncation .slice(0, maxLen) counts UTF-16 code units, so an emoji straddling the cut point produces a lone surrogate that renders as � in usage cost CLI output. Use [...str] to count full code points instead. Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
Codex review: needs maintainer review before merge. Reviewed July 7, 2026, 6:44 AM ET / 10:44 UTC. Summary PR surface: Source +1, Tests +19. Total +20 across 2 files. Reproducibility: yes. source-level. Current main applies raw UTF-16 slicing in Review metrics: none identified. Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Next step before merge
Security Review detailsBest possible solution: Land the helper-based truncation and regression test after the normal exact-head CI, base freshness, and maintainer review gates clear. Do we have a high-confidence way to reproduce the issue? Yes, source-level. Current main applies raw UTF-16 slicing in Is this the best way to solve the issue? Yes. The latest patch uses the existing UTF-16-safe helper, preserving the existing code-unit cap and avoiding the code-point-counting behavior drift from the original spread implementation. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 942b44966107. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +1, Tests +19. Total +20 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
Review history (1 earlier review cycle)
|
|
Land-ready at exact reviewed head
No docs or changelog change is required for this internal log-detail boundary correction. |
|
Merged via squash.
|
…ontent truncation (openclaw#101517) * fix(session-cost-usage): keep emoji / surrogate pairs intact during content truncation .slice(0, maxLen) counts UTF-16 code units, so an emoji straddling the cut point produces a lone surrogate that renders as � in usage cost CLI output. Use [...str] to count full code points instead. Co-Authored-By: Claude Opus 4.8 <[email protected]> * test(usage): cover UTF-16-safe log truncation --------- Co-authored-by: Claude Opus 4.8 <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
…ontent truncation (openclaw#101517) * fix(session-cost-usage): keep emoji / surrogate pairs intact during content truncation .slice(0, maxLen) counts UTF-16 code units, so an emoji straddling the cut point produces a lone surrogate that renders as � in usage cost CLI output. Use [...str] to count full code points instead. Co-Authored-By: Claude Opus 4.8 <[email protected]> * test(usage): cover UTF-16-safe log truncation --------- Co-authored-by: Claude Opus 4.8 <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
What Problem This Solves
loadSessionLogsinsrc/infra/session-cost-usage.tstruncates long content with.slice(0, 2000), which counts UTF-16 code units. When an emoji (e.g. 📊) straddles the cut boundary at position 2000,.slice()produces a lone surrogate (e.g.\ud83d) that renders as�in usage cost CLI output.This is user-visible: the content source is
message.contentfrom real session transcripts, so any user who sends a long message containing emoji near the 2000-char boundary can hit this.Why This Change Was Made
Replace
.slice(0, maxLen)with[...content].slice(0, maxLen).join("")so the truncation counts full code points instead of UTF-16 code units. This matches the existingshortenTexthelper insrc/commands/text-format.ts:5and thesanitizeConsoleTexthelper insrc/agents/console-sanitize.ts:25-31, both of which already useArray.from()/ spread to avoid surrogate splits.User Impact
Users with emoji near the character boundary in usage cost CLI output will no longer see
�replacement characters.Evidence
Real environment tested: local OpenClaw source checkout + live Gateway, Node v24.13.1, PR head.
Exact steps after this patch:
Evidence after fix:
Live Gateway verification: Sent the same 2000-char emoji-boundary message through the running Gateway to confirm the fix resolves the
�artifact in usage log output.Observed result after fix: the truncation no longer splits a surrogate pair at the boundary; all output code points remain well-formed.
What was not tested: unrelated truncation sites in other source files remain unchanged.
Regression Test Plan
AI-assisted.