fix(codex): keep CLI session preview text on code-point boundaries#96582
Conversation
truncateText shortened the cached lastMessage preview with value.slice(0, max - 3), which can cut a surrogate pair in half and emit a lone surrogate into the codex CLI session list JSON. Use the shared truncateUtf16Safe helper so truncation falls back to a whole code-point boundary. Add regressions for both the history.jsonl and sessions/**/*.jsonl preview paths.
|
Codex review: needs maintainer review before merge. Reviewed June 26, 2026, 12:04 AM ET / 04:04 UTC. Summary PR surface: Source +4, Tests +61. Total +65 across 2 files. Reproducibility: yes. Current main sends both Codex history and rollout session-file preview text through a helper that raw-slices UTF-16, and the PR body demonstrates the boundary case through the real node command handler. 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. Next step before merge
Security Review detailsBest possible solution: Land or adopt the helper-based fix so Codex CLI session previews are truncated at UTF-16 code point boundaries across both history and rollout sources. Do we have a high-confidence way to reproduce the issue? Yes. Current main sends both Codex history and rollout session-file preview text through a helper that raw-slices UTF-16, and the PR body demonstrates the boundary case through the real node command handler. Is this the best way to solve the issue? Yes. The shared local AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 0fdfc9f65ff4. Label changesLabel justifications:
Evidence reviewedPR surface: Source +4, Tests +61. Total +65 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
|
…penclaw#96582) truncateText shortened the cached lastMessage preview with value.slice(0, max - 3), which can cut a surrogate pair in half and emit a lone surrogate into the codex CLI session list JSON. Use the shared truncateUtf16Safe helper so truncation falls back to a whole code-point boundary. Add regressions for both the history.jsonl and sessions/**/*.jsonl preview paths.
…penclaw#96582) truncateText shortened the cached lastMessage preview with value.slice(0, max - 3), which can cut a surrogate pair in half and emit a lone surrogate into the codex CLI session list JSON. Use the shared truncateUtf16Safe helper so truncation falls back to a whole code-point boundary. Add regressions for both the history.jsonl and sessions/**/*.jsonl preview paths.
…penclaw#96582) truncateText shortened the cached lastMessage preview with value.slice(0, max - 3), which can cut a surrogate pair in half and emit a lone surrogate into the codex CLI session list JSON. Use the shared truncateUtf16Safe helper so truncation falls back to a whole code-point boundary. Add regressions for both the history.jsonl and sessions/**/*.jsonl preview paths.
…penclaw#96582) truncateText shortened the cached lastMessage preview with value.slice(0, max - 3), which can cut a surrogate pair in half and emit a lone surrogate into the codex CLI session list JSON. Use the shared truncateUtf16Safe helper so truncation falls back to a whole code-point boundary. Add regressions for both the history.jsonl and sessions/**/*.jsonl preview paths.
Summary: The Codex CLI session preview (
lastMessage) was truncated withvalue.slice(0, max-3)on raw UTF-16, tearing an emoji into a lone surrogate in the session-list JSON. This usestruncateUtf16Safeto truncate on a code-point boundary.What Problem This Solves
Codex CLI session previews in
extensions/codex/src/node-cli-sessions.tstruncated text with raw UTF-16.slice(). When the preview limit landed between the high and low surrogate of an emoji, the displayedlastMessagecould contain a lone surrogate.Trigger input used for proof:
136ASCII characters🤖(U+1F916, two UTF-16 code units)The old
slice(0, 137) + "..."behavior kept only the high surrogate (0xd83e) before the ellipsis.Fix
truncateTextnow reuses the sharedtruncateUtf16Safehelper fromopenclaw/plugin-sdk/text-utility-runtimeinstead of slicing raw UTF-16 code units.The fix keeps the existing preview width and ellipsis behavior, clamps the safe truncation budget with
Math.max(0, max - 3), and preserves the existing escaping/formatting path by only changing the preview truncation boundary.Evidence
Command run from an isolated proof worktree on
fix/codex-session-preview-surrogate:The demo imports the real
createCodexCliSessionNodeHostCommands()entry point from./extensions/codex/src/node-cli-sessions.ts, writes a temporary Codexhistory.jsonl, invokes the realcodex.cli.sessions.listhandler, and compares old raw.slice()behavior with the fixed handler output.The old path produces a lone high surrogate at the truncation boundary. The fixed real handler output is well-formed UTF-16 and the lone-surrogate check is
false.Tests
Targeted Vitest coverage was added for both Codex preview sources:
Validation run: