openclaw - 💡(How to fix) Fix Control UI: chatLoading skeleton flash on history reload (WSL2 / high-latency localhost) [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#71844Fetched 2026-04-26 05:07:37
View on GitHub
Comments
1
Participants
2
Timeline
3
Reactions
0
Author
Participants
Timeline (top)
closed ×1commented ×1cross-referenced ×1

The chatLoading = true skeleton flash during yc() history reloads causes messages to visibly disappear on turn completion. This was partially addressed in v2026.4.24 (via pendingSessionMessageReloadSessionKey deferral), but the core issue remains: yc() unconditionally sets chatLoading = true, which swaps visible messages for a skeleton placeholder during the async chat.history round-trip.

On native localhost with small sessions, the gap is sub-frame. On WSL2 or any setup with higher localhost latency and larger sessions, it's clearly visible — messages vanish for a beat, then reappear.

Related: #71371 (closed by v2026.4.24, but this is the remaining half of the bug).

Root Cause

In the history reload function (yc in the minified bundle):

e.chatLoading = !0, e.lastError = null;
try {
  let a;
  for (;;) try {
    a = await e.client.request('chat.history', { sessionKey: t, limit: 200 });
    break;
  } catch (i) { /* retry logic */ }
  // ... merge messages via mc() ...
  e.chatStream = null;
} finally {
  e.chatLoading = !1;
}

The chat renderer (qM) checks e.loading and replaces the message list with a skeleton:

${e.loading ? s`<div class="chat-loading-skeleton">...` : /* render actual messages */}

So when yc() fires after turn completion, messages are swapped for skeleton during the async wait. On high-latency localhost (WSL2 virtual network), this is perceptible.

Fix Action

Fix / Workaround

We've applied this patch locally and confirmed it resolves the issue completely.

Code Example

e.chatLoading = !0, e.lastError = null;
try {
  let a;
  for (;;) try {
    a = await e.client.request('chat.history', { sessionKey: t, limit: 200 });
    break;
  } catch (i) { /* retry logic */ }
  // ... merge messages via mc() ...
  e.chatStream = null;
} finally {
  e.chatLoading = !1;
}

---

${e.loading ? s`<div class="chat-loading-skeleton">...` : /* render actual messages */}

---

- e.chatLoading = !0, e.lastError = null;
+ e.chatMessages.length === 0 && (e.chatLoading = !0), e.lastError = null;
RAW_BUFFERClick to expand / collapse

Summary

The chatLoading = true skeleton flash during yc() history reloads causes messages to visibly disappear on turn completion. This was partially addressed in v2026.4.24 (via pendingSessionMessageReloadSessionKey deferral), but the core issue remains: yc() unconditionally sets chatLoading = true, which swaps visible messages for a skeleton placeholder during the async chat.history round-trip.

On native localhost with small sessions, the gap is sub-frame. On WSL2 or any setup with higher localhost latency and larger sessions, it's clearly visible — messages vanish for a beat, then reappear.

Related: #71371 (closed by v2026.4.24, but this is the remaining half of the bug).

Reproduction

  • Environment: WSL2 (Linux 6.6.x-microsoft-standard-WSL2), gateway on loopback (127.0.0.1)
  • Sessions: 20-30+ child sessions, 200k+ tokens
  • Steps: Send any message via Control UI, watch the chat area when the response finalizes
  • Result: Messages flash-disappear (replaced by skeleton), then reappear when chat.history returns
  • VS Code extension: Not affected — only the Control UI web frontend
  • Browser refresh always recovers the messages

Root cause

In the history reload function (yc in the minified bundle):

e.chatLoading = !0, e.lastError = null;
try {
  let a;
  for (;;) try {
    a = await e.client.request('chat.history', { sessionKey: t, limit: 200 });
    break;
  } catch (i) { /* retry logic */ }
  // ... merge messages via mc() ...
  e.chatStream = null;
} finally {
  e.chatLoading = !1;
}

The chat renderer (qM) checks e.loading and replaces the message list with a skeleton:

${e.loading ? s`<div class="chat-loading-skeleton">...` : /* render actual messages */}

So when yc() fires after turn completion, messages are swapped for skeleton during the async wait. On high-latency localhost (WSL2 virtual network), this is perceptible.

Suggested fix

Only show the loading skeleton when there are no messages to display (initial load). When messages already exist, let the history reload happen silently:

- e.chatLoading = !0, e.lastError = null;
+ e.chatMessages.length === 0 && (e.chatLoading = !0), e.lastError = null;

This preserves the skeleton UX on first load while preventing the flash on subsequent reloads. The mc() merge function already handles reconciling server history with local state correctly, so the visual result is identical — just without the flash.

We've applied this patch locally and confirmed it resolves the issue completely.

Environment

OpenClaw versions testedv2026.4.12 through v2026.4.24
OSWSL2 on Windows (Linux 6.6.87.2-microsoft-standard-WSL2)
Gatewayloopback-only, systemd user service
Typical session size20-30 child sessions, 200k+ tokens

extent analysis

TL;DR

Apply the suggested fix by conditionally setting e.chatLoading to true only when there are no messages to display, to prevent the loading skeleton from replacing existing messages during history reloads.

Guidance

  • Verify that the issue is indeed caused by the unconditional setting of e.chatLoading to true in the yc() function, which swaps visible messages for a skeleton placeholder during the async chat.history round-trip.
  • Apply the suggested fix by changing the line e.chatLoading = !0, e.lastError = null; to e.chatMessages.length === 0 && (e.chatLoading = !0), e.lastError = null; to only show the loading skeleton when there are no messages to display.
  • Test the fix in different environments, including WSL2 and native localhost, to ensure that the issue is resolved.
  • Monitor the chat area after applying the fix to verify that messages no longer flash-disappear during history reloads.

Example

The suggested fix can be applied as follows:

- e.chatLoading = !0, e.lastError = null;
+ e.chatMessages.length === 0 && (e.chatLoading = !0), e.lastError = null;

This change ensures that the loading skeleton is only displayed when there are no messages to display, preventing the flash of the skeleton during history reloads.

Notes

The suggested fix has been locally tested and confirmed to resolve the issue completely. However, it is essential to test the fix in different environments to ensure that it works as expected.

Recommendation

Apply the workaround by conditionally setting e.chatLoading to true only when there are no messages to display, as this fix has been locally tested and confirmed to resolve the issue completely.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix Control UI: chatLoading skeleton flash on history reload (WSL2 / high-latency localhost) [1 comments, 2 participants]