fix(http-error-body): keep emoji / surrogate pairs intact during error body truncation#101728
Conversation
…r body truncation Replace .slice(0, limits.maxChars) with truncateUtf16Safe() so that emoji straddling the maxChars boundary don't produce lone surrogates that render as � in error diagnostics. Matches the sibling http-body.ts which already uses truncateUtf16Safe. Co-Authored-By: Claude <[email protected]>
|
Codex review: needs real behavior proof before merge. Reviewed July 7, 2026, 11:03 AM ET / 15:03 UTC. Summary PR surface: Source +6, Tests +26. Total +32 across 2 files. Reproducibility: yes. from source inspection. Current main slices 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:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the narrow helper reuse after contributor proof shows a real HTTP error-body diagnostic with supplementary Unicode remains well-formed, then refresh exact-head checks on current main. Do we have a high-confidence way to reproduce the issue? Yes, from source inspection. Current main slices Is this the best way to solve the issue? Yes. Reusing the existing AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 87fe266c5e4d. Label changesLabel justifications:
Evidence reviewedPR surface: Source +6, Tests +26. Total +32 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 maintainer review complete on exact head
No docs, config, migration, security, or changelog changes are required. No known proof gaps. |
|
Merged via squash.
|
…r body truncation (openclaw#101728) * fix(http-error-body): keep emoji / surrogate pairs intact during error body truncation Replace .slice(0, limits.maxChars) with truncateUtf16Safe() so that emoji straddling the maxChars boundary don't produce lone surrogates that render as � in error diagnostics. Matches the sibling http-body.ts which already uses truncateUtf16Safe. Co-Authored-By: Claude <[email protected]> * test: cover UTF-16 HTTP body branches --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
…r body truncation (openclaw#101728) * fix(http-error-body): keep emoji / surrogate pairs intact during error body truncation Replace .slice(0, limits.maxChars) with truncateUtf16Safe() so that emoji straddling the maxChars boundary don't produce lone surrogates that render as � in error diagnostics. Matches the sibling http-body.ts which already uses truncateUtf16Safe. Co-Authored-By: Claude <[email protected]> * test: cover UTF-16 HTTP body branches --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
…cript truncation UTF-16 safe normalizeCliContextValue (cli-output.ts) and formatSkillExperienceReviewTranscript (experience-review-prompt.ts) both used raw .slice(0, N) / .slice(-N) to truncate user-facing strings. When a truncation boundary falls inside a surrogate pair (emoji), the resulting string contains a lone surrogate — encodeURIComponent throws URIError, JSON serialisers may reject it, and downstream display surfaces can render garbage. Replace .slice(0, 200) with truncateUtf16Safe in normalizeCliContextValue. Replace .slice(0, 6_000) with truncateUtf16Safe and full.slice(-tailBudget) with sliceUtf16Safe(full, tailStart) in formatSkillExperienceReviewTranscript. Follows the pattern established by openclaw#98644, openclaw#102470, openclaw#101728 and the broader UTF-16 safety sweep already applied across the codebase.
…cript truncation UTF-16 safe normalizeCliContextValue (cli-output.ts) and formatSkillExperienceReviewTranscript (experience-review-prompt.ts) both used raw .slice(0, N) / .slice(-N) to truncate user-facing strings. When a truncation boundary falls inside a surrogate pair (emoji), the resulting string contains a lone surrogate — encodeURIComponent throws URIError, JSON serialisers may reject it, and downstream display surfaces can render garbage. Replace .slice(0, 200) with truncateUtf16Safe in normalizeCliContextValue. Replace .slice(0, 6_000) with truncateUtf16Safe and full.slice(-tailBudget) with sliceUtf16Safe(full, tailStart) in formatSkillExperienceReviewTranscript. Follows the pattern established by openclaw#98644, openclaw#102470, openclaw#101728 and the broader UTF-16 safety sweep already applied across the codebase.
…cript truncation UTF-16 safe normalizeCliContextValue (cli-output.ts) and formatSkillExperienceReviewTranscript (experience-review-prompt.ts) both used raw .slice(0, N) / .slice(-N) to truncate user-facing strings. When a truncation boundary falls inside a surrogate pair (emoji), the resulting string contains a lone surrogate — encodeURIComponent throws URIError, JSON serialisers may reject it, and downstream display surfaces can render garbage. Replace .slice(0, 200) with truncateUtf16Safe in normalizeCliContextValue. Replace .slice(0, 6_000) with truncateUtf16Safe and full.slice(-tailBudget) with sliceUtf16Safe(full, tailStart) in formatSkillExperienceReviewTranscript. Follows the pattern established by openclaw#98644, openclaw#102470, openclaw#101728 and the broader UTF-16 safety sweep already applied across the codebase.
…cript truncation UTF-16 safe normalizeCliContextValue (cli-output.ts) and formatSkillExperienceReviewTranscript (experience-review-prompt.ts) both used raw .slice(0, N) / .slice(-N) to truncate user-facing strings. When a truncation boundary falls inside a surrogate pair (emoji), the resulting string contains a lone surrogate — encodeURIComponent throws URIError, JSON serialisers may reject it, and downstream display surfaces can render garbage. Replace .slice(0, 200) with truncateUtf16Safe in normalizeCliContextValue. Replace .slice(0, 6_000) with truncateUtf16Safe and full.slice(-tailBudget) with sliceUtf16Safe(full, tailStart) in formatSkillExperienceReviewTranscript. Follows the pattern established by openclaw#98644, openclaw#102470, openclaw#101728 and the broader UTF-16 safety sweep already applied across the codebase.
…cript truncation UTF-16 safe (#106370) * fix(agents,skills): keep session-identity and experience-review transcript truncation UTF-16 safe normalizeCliContextValue (cli-output.ts) and formatSkillExperienceReviewTranscript (experience-review-prompt.ts) both used raw .slice(0, N) / .slice(-N) to truncate user-facing strings. When a truncation boundary falls inside a surrogate pair (emoji), the resulting string contains a lone surrogate — encodeURIComponent throws URIError, JSON serialisers may reject it, and downstream display surfaces can render garbage. Replace .slice(0, 200) with truncateUtf16Safe in normalizeCliContextValue. Replace .slice(0, 6_000) with truncateUtf16Safe and full.slice(-tailBudget) with sliceUtf16Safe(full, tailStart) in formatSkillExperienceReviewTranscript. Follows the pattern established by #98644, #102470, #101728 and the broader UTF-16 safety sweep already applied across the codebase. * chore: re-trigger CI after PR body update * test(agents,skills): construct transcripts exceeding 60k chars, verify old slices dangle Both experience-review fixtures now exceed EXPERIENCE_REVIEW_MAX_TRANSCRIPT_CHARS so the truncation branch actually fires. Pre-condition assertions prove the old raw .slice(0,6000) / .slice(tailStart) produce isolated surrogates; the production functions (formatSkillExperienceReviewTranscript, formatCliOutputError) do not. cli-output test also adds pre-condition check showing normalizeCliContextValue with raw .slice(0,200) would split a surrogate pair. * test: tighten UTF-16 truncation coverage Co-authored-by: 毛宇豪0668001457 <[email protected]> --------- Co-authored-by: Peter Steinberger <[email protected]>
…cript truncation UTF-16 safe (openclaw#106370) * fix(agents,skills): keep session-identity and experience-review transcript truncation UTF-16 safe normalizeCliContextValue (cli-output.ts) and formatSkillExperienceReviewTranscript (experience-review-prompt.ts) both used raw .slice(0, N) / .slice(-N) to truncate user-facing strings. When a truncation boundary falls inside a surrogate pair (emoji), the resulting string contains a lone surrogate — encodeURIComponent throws URIError, JSON serialisers may reject it, and downstream display surfaces can render garbage. Replace .slice(0, 200) with truncateUtf16Safe in normalizeCliContextValue. Replace .slice(0, 6_000) with truncateUtf16Safe and full.slice(-tailBudget) with sliceUtf16Safe(full, tailStart) in formatSkillExperienceReviewTranscript. Follows the pattern established by openclaw#98644, openclaw#102470, openclaw#101728 and the broader UTF-16 safety sweep already applied across the codebase. * chore: re-trigger CI after PR body update * test(agents,skills): construct transcripts exceeding 60k chars, verify old slices dangle Both experience-review fixtures now exceed EXPERIENCE_REVIEW_MAX_TRANSCRIPT_CHARS so the truncation branch actually fires. Pre-condition assertions prove the old raw .slice(0,6000) / .slice(tailStart) produce isolated surrogates; the production functions (formatSkillExperienceReviewTranscript, formatCliOutputError) do not. cli-output test also adds pre-condition check showing normalizeCliContextValue with raw .slice(0,200) would split a surrogate pair. * test: tighten UTF-16 truncation coverage Co-authored-by: 毛宇豪0668001457 <[email protected]> --------- Co-authored-by: Peter Steinberger <[email protected]>
…cript truncation UTF-16 safe (openclaw#106370) * fix(agents,skills): keep session-identity and experience-review transcript truncation UTF-16 safe normalizeCliContextValue (cli-output.ts) and formatSkillExperienceReviewTranscript (experience-review-prompt.ts) both used raw .slice(0, N) / .slice(-N) to truncate user-facing strings. When a truncation boundary falls inside a surrogate pair (emoji), the resulting string contains a lone surrogate — encodeURIComponent throws URIError, JSON serialisers may reject it, and downstream display surfaces can render garbage. Replace .slice(0, 200) with truncateUtf16Safe in normalizeCliContextValue. Replace .slice(0, 6_000) with truncateUtf16Safe and full.slice(-tailBudget) with sliceUtf16Safe(full, tailStart) in formatSkillExperienceReviewTranscript. Follows the pattern established by openclaw#98644, openclaw#102470, openclaw#101728 and the broader UTF-16 safety sweep already applied across the codebase. * chore: re-trigger CI after PR body update * test(agents,skills): construct transcripts exceeding 60k chars, verify old slices dangle Both experience-review fixtures now exceed EXPERIENCE_REVIEW_MAX_TRANSCRIPT_CHARS so the truncation branch actually fires. Pre-condition assertions prove the old raw .slice(0,6000) / .slice(tailStart) produce isolated surrogates; the production functions (formatSkillExperienceReviewTranscript, formatCliOutputError) do not. cli-output test also adds pre-condition check showing normalizeCliContextValue with raw .slice(0,200) would split a surrogate pair. * test: tighten UTF-16 truncation coverage Co-authored-by: 毛宇豪0668001457 <[email protected]> --------- Co-authored-by: Peter Steinberger <[email protected]>
What Problem This Solves
readResponseBodySnippetinsrc/infra/http-error-body.tstruncates HTTP error response body text with.slice(0, limits.maxChars), which counts UTF-16 code units. When an emoji straddles the cut boundary,.slice()produces a lone surrogate that renders as�in error diagnostics shown to users.The sibling file
src/infra/http-body.tsalready imports and usestruncateUtf16Safefor the same kind of truncation (#101685), buthttp-error-body.tswas missed.Why This Change Was Made
Replace
.slice(0, limits.maxChars)withtruncateUtf16Safe(text, limits.maxChars)at all three call sites (body-less path × 2 + stream path × 1). This matchessrc/infra/http-body.ts:317.User Impact
Error diagnostics from MiniMax VLM and PDF native providers will no longer contain
�when an emoji falls near the character truncation boundary.Evidence
The table-driven regression test asserts the exact surrogate-safe result across
all three reachable branches:
maxBytesmaxBytesExact-head hosted CI is the execution gate for this contributor branch.
AI-assisted.