Skip to content

fix: prevent garbled emoji at remaining text limits#101685

Merged
steipete merged 3 commits into
mainfrom
codex/utf16-boundary-followup
Jul 7, 2026
Merged

fix: prevent garbled emoji at remaining text limits#101685
steipete merged 3 commits into
mainfrom
codex/utf16-boundary-followup

Conversation

@steipete

@steipete steipete commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Related: #101634
Related: #101640
Related: #101641
Related: #101642
Related: #101644
Related: #101646
Related: #101659
Related: #101660

What Problem This Solves

Fixes an issue where users could see a dangling replacement character or malformed text when emoji and other supplementary Unicode characters crossed length limits in Codex diagnostics, Discord persona names, TUI errors, dashboard titles, session summaries, prompt templates, and Control UI labels.

Why This Change Was Made

Uses the existing UTF-16-safe truncation primitive at the remaining distinct bounded-text call sites instead of adding per-surface helpers. The replacement also covers every bound in the embedded-agent observation pipeline and both string and structured-content session discovery paths, while omitting unrelated formatting churn and an ASCII-only sanitizer where surrogate safety is irrelevant.

The Codex adapter behavior was checked directly against upstream app-server protocol and implementation sources: dynamic-tool responses are the documented inputText/inputImage union, unsupported response kinds are locally rendered diagnostics, and app-list errors are arbitrary text. Discord's webhook contract retains the existing 80-code-unit username limit.

User Impact

Emoji and other supplementary characters now remain well-formed when these labels, previews, diagnostics, and source inputs reach their existing length caps. Limits, fallback values, ellipsis behavior, and public configuration remain unchanged.

Evidence

The source PRs are superseded by this clean-main consolidation; contributor credit is preserved in the commit trailers.

Co-authored-by: 0668000787 <[email protected]>
Co-authored-by: ben.li <[email protected]>
Co-authored-by: 廖石荣 0668000909 <[email protected]>
Co-authored-by: 陈志强0668000989 <[email protected]>
@openclaw-barnacle openclaw-barnacle Bot added channel: discord Channel integration: discord app: web-ui App: web-ui gateway Gateway runtime agents Agent runtime and tooling extensions: codex size: M maintainer Maintainer-authored PR labels Jul 7, 2026
@clawsweeper

clawsweeper Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 7, 2026, 9:41 AM ET / 13:41 UTC.

Summary
The branch replaces raw bounded string slicing with UTF-16-safe truncation across Codex app-server diagnostics, Discord persona names, agent observations, session/title summaries, prompt template descriptions, TUI abort diagnostics, and Control UI labels, with regression tests.

PR surface: Source +14, Tests +254. Total +268 across 27 files.

Reproducibility: yes. from source inspection. Current main and the reviewed PR head raw-slice the thread-binding intro webhook persona at the 80-code-unit boundary, so a label with a surrogate-pair emoji crossing that boundary can produce a lone surrogate.

Review metrics: 1 noteworthy metric.

  • Discord persona truncation coverage: 2 changed, 1 related path still raw-sliced. The branch changes reply identity and outbound webhook usernames but leaves the thread-binding intro webhook persona helper on raw UTF-16 slicing.

Stored data model
Persistent data-model change detected: serialized state: extensions/codex/src/app-server/app-inventory-cache.test.ts, serialized state: src/agents/embedded-agent-error-observation.test.ts, serialized state: src/agents/sessions/prompt-templates.test.ts, serialized state: src/infra/session-cost-usage.test.ts. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🌊 off-meta tidepool
Patch quality: 🦪 silver shellfish
Result: blocked by patch quality or review findings.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Add the missing thread-bindings.persona.ts truncateUtf16Safe change and regression test.
  • [P2] Rerun the focused Discord persona tests plus git diff --check after the repair.

Risk before merge

  • [P1] If this PR lands as the canonical consolidation and the focused Discord PR is closed as superseded, Discord thread-binding intro webhook persona names can still show malformed emoji at the 80-code-unit boundary.

Maintainer options:

  1. Complete the consolidation before merge (recommended)
    Port the thread-binding persona helper and regression test from the focused Discord PR so all three Discord persona-name paths are covered before this branch supersedes the focused fixes.
  2. Keep the focused Discord PR as canonical
    Pause this broad consolidation or avoid closing the focused Discord PR until the missing thread-binding persona path is preserved somewhere mergeable.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Port the Discord thread-binding persona helper and test from the focused Discord PR: import truncateUtf16Safe in extensions/discord/src/monitor/thread-bindings.persona.ts, replace the raw 80-code-unit slice in resolveThreadBindingPersona, add regression coverage in extensions/discord/src/monitor/thread-bindings.persona.test.ts, preserve the existing 80-character limit and persona fallback behavior, and rerun the focused Discord tests plus git diff --check.

Next step before merge

  • [P1] The blocker is a narrow mechanical PR-branch repair: add the missing Discord thread-binding persona helper change and regression coverage.

Security
Cleared: The diff only changes bounded string truncation and tests; it does not add dependency, lockfile, workflow, credential, package, or code-execution surface.

Review findings

  • [P2] Cover the thread-binding persona helper — extensions/discord/src/monitor/reply-delivery.ts:76
Review details

Best possible solution:

Land one canonical consolidation only after it includes the Discord thread-binding persona helper and regression test from the focused Discord PR, or keep the focused Discord PR open as the owner for that remaining path.

Do we have a high-confidence way to reproduce the issue?

Yes, from source inspection. Current main and the reviewed PR head raw-slice the thread-binding intro webhook persona at the 80-code-unit boundary, so a label with a surrogate-pair emoji crossing that boundary can produce a lone surrogate.

Is this the best way to solve the issue?

No, not yet. Reusing truncateUtf16Safe is the right fix shape, but this consolidation is incomplete until it covers the thread-binding persona helper or stops superseding the focused Discord fix.

Full review comments:

  • [P2] Cover the thread-binding persona helper — extensions/discord/src/monitor/reply-delivery.ts:76
    This is the same blocker from the prior review: the consolidation changes the bound reply identity and outbound webhook username paths, but the thread-binding intro webhook path still sends resolveThreadBindingPersonaFromRecord(record) as the Discord webhook username, and that helper still raw-slices at 80 UTF-16 code units on this PR head. Please port the thread-bindings.persona.ts helper and test from fix(discord): keep persona names UTF-16 safe #101644 before treating this PR as the canonical consolidation.
    Confidence: 0.92

Overall correctness: patch is incorrect
Overall confidence: 0.88

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against b95b9e448794.

Label changes

Label changes:

  • add merge-risk: 🚨 other: Merging this broad consolidation as the superseding PR could leave one known Discord persona-name path unfixed while focused fixes are retired.

Label justifications:

  • P2: This is a normal-priority user-visible text rendering bug with limited blast radius and a narrow repair path.
  • merge-risk: 🚨 other: Merging this broad consolidation as the superseding PR could leave one known Discord persona-name path unfixed while focused fixes are retired.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🌊 off-meta tidepool and patch quality is 🦪 silver shellfish.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Not applicable: The real-behavior proof gate is not applied to this maintainer-labeled consolidation PR; the remaining blocker is source completeness rather than contributor proof.
Evidence reviewed

PR surface:

Source +14, Tests +254. Total +268 across 27 files.

View PR surface stats
Area Files Added Removed Net
Source 14 32 18 +14
Tests 13 259 5 +254
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 27 291 23 +268

Acceptance criteria:

  • [P1] node scripts/run-vitest.mjs extensions/discord/src/monitor/thread-bindings.persona.test.ts extensions/discord/src/monitor/reply-delivery.test.ts extensions/discord/src/outbound-adapter.test.ts.
  • [P1] git diff --check.
  • [P1] corepack pnpm check:changed.

What I checked:

Likely related people:

  • xingzhou: Current blame for resolveBindingIdentity, resolveDiscordWebhookIdentity, and resolveThreadBindingPersona points to commit f14eb97. (role: introduced current raw helper in main history; confidence: medium; commits: f14eb97ae7c5; files: extensions/discord/src/monitor/reply-delivery.ts, extensions/discord/src/outbound-adapter.ts, extensions/discord/src/monitor/thread-bindings.persona.ts)
  • ly85206559: The focused Discord persona PR head contains the missing thread-bindings.persona.ts change and regression test that this consolidation should preserve. (role: focused source-fix contributor; confidence: medium; commits: 89d8eb2b487b; files: extensions/discord/src/monitor/thread-bindings.persona.ts, extensions/discord/src/monitor/thread-bindings.persona.test.ts)
  • Peter Steinberger: Recent history for the affected Discord reply/outbound files shows repeated Discord refactors and the current consolidation branch was authored under the same account, making this a practical routing candidate. (role: recent Discord area contributor; confidence: medium; commits: 73e497f9be08, c7347a492ee2, 8edbb1b26460; files: extensions/discord/src/monitor/reply-delivery.ts, extensions/discord/src/outbound-adapter.ts)
  • Vincent Koc: Recent file history includes adjacent Discord outbound/release-stabilization touches, useful as a low-confidence fallback route for Discord behavior review. (role: adjacent Discord contributor; confidence: low; commits: e085fa1a3ffd; files: extensions/discord/src/outbound-adapter.ts, extensions/discord/src/monitor/reply-delivery.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (1 earlier review cycle)
  • reviewed 2026-07-07T13:33:26.194Z sha 853d50f :: needs real behavior proof before merge. :: [P2] Cover the thread-binding persona helper

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 7, 2026
@clawsweeper clawsweeper Bot added the P2 Normal backlog priority with limited blast radius. label Jul 7, 2026
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. label Jul 7, 2026
@steipete

steipete commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Land-ready proof for exact head 8b0258c1531d890500d8594354ad1c1ab6aaa5bb:

  • Focused Testbox command:
    node scripts/crabbox-wrapper.mjs run --provider blacksmith-testbox --id tbx_01kwyae3xcaszr9ckk2z1bg0ew --timing-json --shell -- "corepack pnpm test extensions/codex/src/app-server/app-inventory-cache.test.ts extensions/codex/src/app-server/run-attempt.test.ts extensions/discord/src/monitor/reply-delivery.test.ts extensions/discord/src/outbound-adapter.test.ts src/tui/tui-event-handlers.test.ts src/agents/embedded-agent-error-observation.test.ts src/gateway/dashboard-session-title.test.ts src/infra/session-cost-usage.test.ts src/agents/sessions/prompt-templates.test.ts ui/src/app/custom-theme.test.ts ui/src/pages/chat/components/chat-tool-cards.test.ts ui/src/pages/workboard/data.test.ts ui/src/pages/usage/view.test.ts"
    Result: 653 tests passed across seven Vitest shards.
  • Corrected UI fixture rerun: corepack pnpm test ui/src/pages/usage/view.test.ts — 8 passed.
  • Prompt-template cleanup rerun: corepack pnpm test src/agents/sessions/prompt-templates.test.ts — 1 passed; node scripts/report-test-temp-creations.mjs --base origin/main --head HEAD --json --fail-on-findings returned [].
  • Changed-surface gate: corepack pnpm check:changed — passed in 10m28s, including four tsgo projects, core/extensions oxlint, import-cycle checks, and architecture guards.
  • git diff --check — passed.
  • Exact-head CI: https://github.com/openclaw/openclaw/actions/runs/28870356599 — success.
  • Exact-head autoreview: clean, 0.98 confidence.
  • Native OPENCLAW_TESTBOX=1 scripts/pr prepare-run 101685 — passed.

Dependency proof: inspected Codex app-server dynamic-tool docs/protocol/decoder and app-list error construction directly (codex-rs/app-server/README.md:1555-1603, codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallOutputContentItem.ts:1-5, codex-rs/app-server/src/dynamic_tools.rs:16-90, codex-rs/app-server/src/request_processors/apps_processor.rs:190-227, and codex-rs/app-server-protocol/src/protocol/common.rs:1486-1490). Discord's official webhook contract retains the existing 80-unit username limit.

Known gap: no live Discord send or Codex server session was run because neither external protocol changes; exact caller tests and direct upstream contract inspection cover the bounded-text behavior.

@steipete
steipete merged commit 176fee5 into main Jul 7, 2026
117 checks passed
@steipete
steipete deleted the codex/utf16-boundary-followup branch July 7, 2026 13:49
@steipete

steipete commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 8, 2026
* fix: keep remaining bounded text UTF-16 safe

Co-authored-by: 0668000787 <[email protected]>
Co-authored-by: ben.li <[email protected]>
Co-authored-by: 廖石荣 0668000909 <[email protected]>
Co-authored-by: 陈志强0668000989 <[email protected]>

* test: use tracked prompt template fixture

---------

Co-authored-by: 0668000787 <[email protected]>
Co-authored-by: ben.li <[email protected]>
Co-authored-by: 廖石荣 0668000909 <[email protected]>
Co-authored-by: 陈志强0668000989 <[email protected]>
giodl73-repo pushed a commit to giodl73-repo/openclaw that referenced this pull request Jul 8, 2026
* fix: keep remaining bounded text UTF-16 safe

Co-authored-by: 0668000787 <[email protected]>
Co-authored-by: ben.li <[email protected]>
Co-authored-by: 廖石荣 0668000909 <[email protected]>
Co-authored-by: 陈志强0668000989 <[email protected]>

* test: use tracked prompt template fixture

---------

Co-authored-by: 0668000787 <[email protected]>
Co-authored-by: ben.li <[email protected]>
Co-authored-by: 廖石荣 0668000909 <[email protected]>
Co-authored-by: 陈志强0668000989 <[email protected]>
steipete added a commit that referenced this pull request Jul 9, 2026
* fix(ui): keep workboard capture text truncation UTF-16 safe

Replace raw .slice(0, N - 3) with truncateUtf16Safe in
clampSessionCaptureText and clampSessionCaptureTitle to prevent
lone surrogates when emoji cross the truncation boundary.

Completes the UTF-16 hardening started in #101685, which added the
import but only covered buildCardSessionLabel.

* test(ui): cover workboard UTF-16 boundaries

* docs(changelog): note Workboard UTF-16 fix

* docs(changelog): leave release notes to release flow

---------

Co-authored-by: Peter Steinberger <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 10, 2026
…#102544)

* fix(ui): keep workboard capture text truncation UTF-16 safe

Replace raw .slice(0, N - 3) with truncateUtf16Safe in
clampSessionCaptureText and clampSessionCaptureTitle to prevent
lone surrogates when emoji cross the truncation boundary.

Completes the UTF-16 hardening started in openclaw#101685, which added the
import but only covered buildCardSessionLabel.

* test(ui): cover workboard UTF-16 boundaries

* docs(changelog): note Workboard UTF-16 fix

* docs(changelog): leave release notes to release flow

---------

Co-authored-by: Peter Steinberger <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui channel: discord Channel integration: discord extensions: codex gateway Gateway runtime maintainer Maintainer-authored PR merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. P2 Normal backlog priority with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: M status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant