fix(session-cost-usage): keep emoji / surrogate pairs intact during first user message truncation#101659
Conversation
…irst user message truncation The discoverAllSessions helper in session-cost-usage.ts truncates the first user message with .slice(0, 100), which counts UTF-16 code units. When an emoji straddles the cut boundary, .slice() produces a lone surrogate that renders as � in session titles and usage API output. Replace .slice(0, 100) with the already-imported truncateUtf16Safe() to avoid splitting surrogate pairs at the boundary. Co-Authored-By: Claude <[email protected]>
|
Codex review: needs real behavior proof before merge. Reviewed July 7, 2026, 9:00 AM ET / 13:00 UTC. Summary PR surface: Source 0, Tests +41. Total +41 across 2 files. Reproducibility: yes. from source inspection: current main slices first user messages by UTF-16 code units at 100, and an emoji crossing that boundary leaves a dangling surrogate. I did not run a live reproduction in this read-only review, but the path and boundary condition are deterministic. 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:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the helper-based truncation at the two missed discovery sites after the contributor adds redacted real behavior proof and exact-head merge gates pass. Do we have a high-confidence way to reproduce the issue? Yes from source inspection: current main slices first user messages by UTF-16 code units at 100, and an emoji crossing that boundary leaves a dangling surrogate. I did not run a live reproduction in this read-only review, but the path and boundary condition are deterministic. Is this the best way to solve the issue? Yes. Using the existing shared UTF-16-safe truncation helper at the two missed discovery sites preserves the current code-unit cap and is narrower than adding a new spread/code-point truncation path. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 4e29b2f5ab4b. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source 0, Tests +41. Total +41 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
|
|
Closed as superseded by #101685, landed in |
What Problem This Solves
discoverAllSessionsinsrc/infra/session-cost-usage.tstruncates the first user message with.slice(0, 100), which counts UTF-16 code units. When an emoji (e.g. 🦞) straddles the cut boundary at position 100,.slice()produces a lone surrogate (e.g.\uD83E) that renders as�in session titles, session list API responses, and usage CLI output.The file already imports
truncateUtf16Safe(added by #101517 forloadSessionLogs), but the two.slice(0, 100)calls indiscoverAllSessionswere missed.Why This Change Was Made
Replace
.slice(0, 100)withtruncateUtf16Safe(text, 100)so the truncation avoids splitting surrogate pairs at the boundary. This matches:loadSessionLogsalready usingtruncateUtf16Safe(line 2756)User Impact
Session titles derived from the first user message (when no explicit label is set) will no longer contain
�replacement characters when an emoji falls near the 100-char boundary.Evidence
Real environment tested: local OpenClaw source checkout, Node v24.
Exact steps after this patch:
Regression test:
pnpm test -- src/infra/session-cost-usage.test.tsWhat was not tested: other truncation sites in the same file remain unchanged and are already covered by existing tests.
AI-assisted.