openclaw - ✅(Solved) Fix [Bug]: Control UI webchat flashes credentials gate on tab re-focus due to immediate connected=false render [1 pull requests, 1 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#72500Fetched 2026-04-27 05:29:41
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1

Every time the browser tab loses and regains focus, the webchat briefly flashes the full credentials/login gate before reconnecting and restoring the normal chat UI. On fast connections this lasts ~300–800ms; on slower connections (Pi-class hardware) it is more visible.

Root Cause

The app has two render states — connected (show chat) or not connected (show credentials gate) — with no intermediate "reconnecting" state. Browser tab visibility changes trigger WebSocket closes, which set `connected = false`, which immediately re-renders the full credentials gate. A debounce of even 1–2 seconds on the `connected = false` transition would eliminate the flash for normal tab switches.

PR fix notes

PR #72522: fix(control-ui): keep chat UI mounted across transient reconnects

Description (problem / solution / changelog)

Fixes #72500.

Problem

Webchat flashes the full credentials gate every time the browser tab loses and regains focus. On slower hardware (Pi-class) the flash lasts long enough that it looks like a forced logout. Same flash happens on any transient WebSocket close — gateway restart, network blip, brief sleep.

Root cause is exactly what @brandco diagnosed:

  1. app-render.ts renders renderLoginGate(state) unconditionally on !state.connected.
  2. app-gateway.ts flips connected = false immediately on every WebSocket close, including expected transient closes when the tab backgrounds.
  3. There is no distinction between "never authenticated" and "temporarily disconnected from a known-good session."

Fix

Add a sticky hasEverConnected flag on the gateway host (and on AppViewState / the Lit element). It latches to true on the first successful hello handshake. The renderer now only shows the credentials gate when the user has both never connected AND is currently disconnected:

if (!state.connected && !state.hasEverConnected) {
  return html` ${renderLoginGate(state)} ${renderGatewayUrlConfirmation(state)} `;
}

For genuine re-auth (token rotated, password changed, pairing revoked) the flag is cleared in the onClose handler when the close detail code matches an auth/pairing failure. New helper isAuthFailureDetailCode() catches AUTH_*, DEVICE_AUTH_*, PAIRING_REQUIRED, DEVICE_IDENTITY_REQUIRED, and the CONTROL_UI_* identity errors from connect-error-details.ts.

Plain transport closes (1006, 1012 service restart, network blips) keep the flag set, so the chat UI stays mounted. The existing sidebar renderSidebarConnectionStatus already surfaces the offline state via the status dot, satisfying the "non-blocking reconnecting indicator" expectation in the issue without adding a new banner.

Tests

pnpm test ui/src/ui/
# 661 passed (661)

Three new tests in app-gateway.node.test.ts:

  • Sticky flag survives transient 1006 closes and reconnect cycles.
  • AUTH_TOKEN_MISMATCH close clears the flag → gate reappears.
  • PAIRING_REQUIRED close clears the flag.

Diff

5 files, +115/-1.

FileChange
ui/src/ui/app-gateway.tsAdd hasEverConnected to GatewayHost; set in onHello; conditionally clear in onClose; new isAuthFailureDetailCode helper.
ui/src/ui/app-render.tsGate condition now !connected && !hasEverConnected.
ui/src/ui/app-view-state.tsAdd hasEverConnected to view state.
ui/src/ui/app.tsAdd @state() hasEverConnected = false to the Lit element.
ui/src/ui/app-gateway.node.test.tsInitialize flag + 3 new tests.

Out of scope

The issue also suggested a 1–2s debounce on the connected = false transition. That's not needed here — the sticky flag already absorbs the transition without any timing dependency, which is more robust on Pi-class hardware where reconnect latency varies wildly.

cc @brandco — would appreciate confirmation this fixes the flash on your Pi 5 setup.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • ui/src/ui/app-gateway.node.test.ts (modified, +85/-0)
  • ui/src/ui/app-gateway.ts (modified, +54/-0)
  • ui/src/ui/app-render.ts (modified, +8/-1)
  • ui/src/ui/app-view-state.ts (modified, +6/-0)
  • ui/src/ui/app.ts (modified, +6/-0)
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

Every time the browser tab loses and regains focus, the webchat briefly flashes the full credentials/login gate before reconnecting and restoring the normal chat UI. On fast connections this lasts ~300–800ms; on slower connections (Pi-class hardware) it is more visible.

Steps to reproduce

  1. Open the Control UI webchat with an authenticated session
  2. Switch to another app or browser tab for any duration
  3. Switch back to the Control UI tab
  4. Observe the credentials gate (WebSocket URL + token + password fields) flash briefly before the chat UI appears

Expected behavior

Switching back to a previously authenticated tab should show a brief "reconnecting..." indicator overlaid on the existing chat content, or no transition at all if reconnection is fast. The full credentials gate should only appear when the user has never connected or when auth explicitly fails (401/403).

Actual behavior

The credentials gate appears on every reconnect cycle because `app-render.ts` renders `renderLoginGate()` unconditionally whenever `state.connected === false`:

```typescript // app-render.ts:666 if (!state.connected) { return html`${renderLoginGate(state)} ${renderGatewayUrlConfirmation(state)}`; } ```

And `app-gateway.ts` sets `connected = false` immediately on any WebSocket close — including expected transient browser-initiated closes when the tab goes to background — with no debounce or "reconnecting" intermediate state:

```typescript // app-gateway.ts:266 — set at start of every connectGateway() call host.connected = false;

// app-gateway.ts:347 — set in onClose handler host.connected = false; // immediately triggers full re-render to credentials gate ```

There is no distinction between "never authenticated" and "temporarily disconnected from a known-good session." Both states render the same credentials gate.

Root cause

The app has two render states — connected (show chat) or not connected (show credentials gate) — with no intermediate "reconnecting" state. Browser tab visibility changes trigger WebSocket closes, which set `connected = false`, which immediately re-renders the full credentials gate. A debounce of even 1–2 seconds on the `connected = false` transition would eliminate the flash for normal tab switches.

Suggested fix

  1. Add a third `reconnecting` state distinct from `disconnected` (never auth'd or auth failed)
  2. Only show the full credentials gate when: (a) no prior successful connection exists in this session, or (b) the server explicitly returns 401/403
  3. For transient disconnects from a known-good session, show a non-blocking "reconnecting..." banner over existing chat content
  4. Debounce the `connected = false` render trigger by ~1–2 seconds to absorb brief interruptions

OpenClaw version

2026.4.24 (cbcfdf6)

Operating system

Raspberry Pi 5 (Linux 6.12 arm64) — also reproducible on any OS with the webchat UI

Install method

npm global

Screenshot

The attached screenshot shows the credentials gate that appears during the flash (captured from a fresh headless session to demonstrate the state):

Before (bug state — what appears during flash): Full credentials form with WebSocket URL, token, and password fields Expected after (not shown): Normal chat interface restored without credentials interruption

extent analysis

TL;DR

Implement a debounce on the connected = false transition and introduce a "reconnecting" state to prevent the credentials gate from flashing when the browser tab regains focus.

Guidance

  • Introduce a third reconnecting state in the app to handle transient disconnects from a known-good session.
  • Modify the app-render.ts to conditionally render the renderLoginGate() based on the new reconnecting state.
  • Add a debounce of 1-2 seconds on the connected = false transition to absorb brief interruptions.
  • Update the app-gateway.ts to set an intermediate "reconnecting" state instead of immediately setting connected = false on WebSocket close.

Example

// app-render.ts
if (state.reconnecting) {
  return html`<div>Reconnecting...</div>`;
} else if (!state.connected) {
  return html`${renderLoginGate(state)} ${renderGatewayUrlConfirmation(state)}`;
}

Notes

The suggested fix assumes that the introduction of a "reconnecting" state and a debounce on the connected = false transition will eliminate the flash of the credentials gate. However, the exact implementation details may vary depending on the specific requirements of the app.

Recommendation

Apply the suggested workaround by introducing a "reconnecting" state and debouncing the connected = false transition to prevent the credentials gate from flashing when the browser tab regains focus. This approach addresses the root cause of the issue and provides a better user experience.

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…

FAQ

Expected behavior

Switching back to a previously authenticated tab should show a brief "reconnecting..." indicator overlaid on the existing chat content, or no transition at all if reconnection is fast. The full credentials gate should only appear when the user has never connected or when auth explicitly fails (401/403).

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 - ✅(Solved) Fix [Bug]: Control UI webchat flashes credentials gate on tab re-focus due to immediate connected=false render [1 pull requests, 1 participants]