Skip to content

[Bug]: WebChat UI freezes on large sessions — synchronous markdown rendering of 200 messages blocks main thread #11890

Description

@BernardoCama

[Bug]: WebChat UI freezes on large sessions — synchronous markdown rendering of 200 messages blocks main thread

Summary

The WebChat Control UI becomes completely unresponsive (browser tab freezes for 5-10+ seconds) on long-lived sessions when any tool call completes. Telegram and other channels continue working normally — confirming this is a client-side rendering issue, not a gateway problem.

The root cause is loadChatHistory() fetching up to 200 messages and running marked.parse() + DOMPurify.sanitize() synchronously on the main thread for every message. On sessions that have grown to 400KB–1.9MB over weeks of use, this creates a main-thread bomb that locks the browser tab.

Steps to reproduce

  1. Use OpenClaw via WebChat for several weeks (sessions grow to 400KB–1.9MB)
  2. Ask the agent to perform any tool call (e.g., cron.list, file read, etc.)
  3. When the tool call completes (state === "final"), the UI calls loadChatHistory()
  4. Browser tab freezes for 5-10+ seconds (or indefinitely on very large sessions)
  5. The node.list polling (every 5s) compounds the freeze by triggering additional re-renders
  6. Only way to recover is creating a new session

Expected behavior

The WebChat UI should remain responsive regardless of session size. Tool call completions should not trigger a full synchronous re-render of the entire chat history.

Actual behavior

  • Browser tab freezes completely (no scrolling, no clicking, no input)
  • Telegram channel continues to work fine during the freeze (gateway is healthy)
  • DevTools performance trace shows ~7 seconds of synchronous JS execution in the loadChatHistory()marked.parse()DOMPurify.sanitize() → Lit repeat() + unsafeHTML() rendering pipeline
  • After the initial freeze, node.list polling triggers further full re-renders, preventing recovery

Root cause analysis

When a chat run completes (state === "final" at line ~214 in webchat source), the UI calls loadChatHistory() which fetches up to 200 messages from the server. Each message then gets:

  1. Markdown parsed via marked.parse() — synchronous, on main thread
  2. Sanitized via DOMPurify.sanitize() — synchronous, on main thread
  3. Rendered into DOM via Lit's repeat() + unsafeHTML() — synchronous

With 200 messages containing code blocks and long tool outputs, this is a synchronous main-thread bomb.

The MARKDOWN_PARSE_LIMIT is 40K chars per message, and the markdown cache holds 200 entries — but on a fresh history load after tool completion, nothing is cached, so all 200 messages get parsed from scratch.

Note: The existing markdown caching fix (commit d57cb2e — "cache Control UI markdown rendering + memoize chat text extraction to reduce Safari typing jank") does not cover the full history reload path triggered by tool call completion.

Proposed fixes

  1. Virtualize the message list — only render messages visible in the viewport (biggest win)
  2. Async/chunked markdown rendering — parse messages in batches via requestIdleCallback or requestAnimationFrame
  3. Reduce history fetch limitCHAT_HISTORY_RENDER_LIMIT and the server limit: 200 could be lowered to ~50 for immediate relief
  4. Skip re-render on node.list — the 5-second node polling should not trigger a full chat re-render if nodes didn't change
  5. Persist markdown cache across history reloads — so returning to a session doesn't re-parse everything

Environment

  • OpenClaw version: 2026.2.3-1
  • OS: macOS
  • Browser: Chrome (latest)
  • Provider: Anthropic (Claude)
  • Session sizes: 400KB to 1.9MB after weeks of use
  • Interface: WebChat (Control UI)
  • Other channels (Telegram): unaffected

Additional context

This is a classic "works fine with small data, dies at scale" issue. When first installed, sessions are empty/tiny and the 200-message synchronous render is fast. After weeks of daily use with tool calls generating large outputs, sessions grow to megabytes, and the rendering pipeline becomes unusable.

Related issues for context:

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingstaleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions