Skip to content

fix(server): html_preview no longer splits an emoji in a long console line - #29844

Merged
maria-rcks merged 1 commit into
pingdotgg:mainfrom
saphid:fix/html-preview-console-emoji
Nov 3, 2026
Merged

maria-rcks merged 1 commit into
pingdotgg:mainfrom
saphid:fix/html-preview-console-emoji

Conversation

@saphid

@saphid saphid commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Problem

html_preview returns the page's console messages to the agent and cuts each one at 500 characters. The cut counts UTF-16 units, so when an emoji sits across character 500, the message keeps only the first half of it: a lone high surrogate. The MCP result then carries that half as a \ud83d escape, and strict JSON parsers reject the whole tool response. Rust serde_json, which Codex's and Grok's MCP clients use, fails with unexpected end of hex escape.

So if a page logs a long line with emoji in it, such as console.log(JSON.stringify(data)) on a dashboard, the agent's preview call can fail. The agent loses the screenshot and contentHeight, not only the log line.

Change

When the last kept unit is a high surrogate, the cut steps back one unit. This is the same guard that #29600 adds to McpHttpServer.cutText for preview_snapshot, and that #29168 and #29633 use for their own cuts. Messages under the cap, and cuts that don't split a pair, come out the same as before.

Scope and approval

Very small, focused fix for an obvious bug, so no prior issue: one cut site, 5 non-test lines, using a pattern this repo already uses. Overlap search: the earlier surrogate PRs (#29158, #29163, #29168, #29563, #29572, #29600, #29633, #29647, #29653, #29830) each fix a different cut site. #29600 covers preview_snapshot, which is the server browser, not the html_preview headless renderer. Open PRs that touch headlessChrome.ts (#25276, #26570) do not change this line.

Verification

  • Red on main: the extended fake-browser case in HtmlRender.test.ts (the only preview flow that runs in CI) fails with AssertionError: expected false to be true on isWellFormed().
  • Green: vp test run src/htmlRender/HtmlRender.test.ts, 8 passed and 3 skipped (the real-browser cases need T3CODE_TEST_HEADLESS_SHELL).
  • Real headless Chrome 156, through a temporary env-gated test that is not committed: on main, console.log("x" + "😀".repeat(300)) comes back with length 501, ends in "\ude00\ud83d…" and is not well-formed. With the fix it is well-formed.
  • Downstream: serde_json 1.0.154 from_str on the JSON-RPC result that main produces gives ERR unexpected end of hex escape.
  • vp run check:changed: all pass (format, lint, knip, typecheck, tests).
  • Not checked: a live Codex or Grok turn that calls html_preview.

Reviewed with sol-loop: 1 round with GPT-6.1 Sol on high. Sol verdict: approved.

Created with Claude Opus 5.5 in Claude Code (T3 Code).

🤖 Generated with Claude Code

… line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XS 0-9 changed lines (additions + deletions). labels Nov 3, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at ab034ab

Macroscope's review found this PR approvable — The change avoids splitting an emoji when truncating long HTML-preview console messages. Existing output limits and browser behavior remain unchanged, keeping the impact narrowly confined to console-text formatting.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Walkthrough

Console-message truncation now avoids splitting a UTF-16 surrogate pair at the 500-character limit. A fake-browser test checks that a long emoji message produces a well-formed preview console message of length 500.

Changes: HTML preview console messages

Layer / File(s) Summary
Surrogate-safe console truncation
apps/server/src/htmlRender/headlessChrome.ts, apps/server/src/htmlRender/HtmlRender.test.ts
Truncation excludes a high surrogate at the 500-character boundary before appending an ellipsis. The fake-browser test checks the resulting message’s well-formedness and length.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Possibly related PRs

  • pingdotgg/t3code#25099: Refactors headlessChrome.receive, including the console-event processing path changed here.

Suggested labels: bugfix, size:XS

Suggested reviewers: juliusmarminge, maria-rcks

Merge Risk: Minimal · up to ab034

No actionable merge-blocking risk remains in the reviewed changes.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main fix: preventing html_preview from splitting an emoji in a long console line.
Description check Passed The description includes all required sections. It explains the problem, the implementation, the focused scope, approval rationale, verification results, and limitations.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

ast-grep (0.51.1)

ast-grep could not parse rule config: /ast-grep-rules/ast-grep-essentials/rules/java/security/unencrypted-socket-java.yml

Usage-based review receipt

Note

This review was completed with usage-based billing: files reviewed beyond your plan's included limits are billed at $0.25/file. View usage-based billing.

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between e0f69e9 and ab034ab.

Files selected for processing: 2
  • apps/server/src/htmlRender/HtmlRender.test.ts
  • apps/server/src/htmlRender/headlessChrome.ts

Limit details: You’ve used all 10 included reviews currently available.

@maria-rcks

Copy link
Copy Markdown
Collaborator

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified as-is at ab034ab598b2e1012798f73a836bf13b4a65ea0b. no code changes.

ran vp i and a real background dev server in each checkout, sequentially. drove the web app with repo playwright and /usr/bin/google-chrome, added the real git repository and a thread through the ui, selected codex gpt-6.1-sol, and submitted identical html through its actual html_preview mcp tool.

  • github/main at 61c3794c32b0eeebfac09add43e403618c65d2d7: console.log("x" + "😀".repeat(300)) produced Deserialize error: unexpected end of hex escape. codex lost the screenshot, content height and console result.
  • this head: the same html succeeded. codex received a 640 × 299 png, contentHeight: 299, and well-formed console text of 500 utf-16 units ending in an ellipsis. confirmed against the raw provider result and inspected the returned png.
  • nearby behavior passed on both: short emoji text, 600 ascii characters, emoji aligned with the cutoff, and a warning. their console results and 640 × 224 screenshot were identical across main and this head.
  • existing focused checks through blacksmith: vp test run apps/server/src/htmlRender/HtmlRender.test.ts, 8 passed and 3 skipped. current ci has no pending or failing checks.

both servers used T3CODE_DEV_SEED=0 and isolated state. this vm's desktop dbus initially stalled headless navigation; restarting with DBUS_SESSION_BUS_ADDRESS=disabled: resolved that on both heads without changing source. main was stopped before starting this head.

main: codex receives the json parser error; nearby cases succeed

pr: the same boundary case and nearby cases return successfully

sanitized runtime transcript

Not verified

native desktop and ios/android clients, macos/windows hosts, other providers, and remote/relay connections were not exercised on this linux vm. the worker's three browser-gated tests were skipped because T3CODE_TEST_HEADLESS_SHELL was unset; the real browser/client/codex path above was exercised separately. check:changed was not rerun because verification required no code changes.

@maria-rcks
maria-rcks merged commit 7767a95 into pingdotgg:main Nov 3, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XS 0-9 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants