Skip to content

fix(control-ui): keep the dashboard mounted with a reconnect banner on gateway drops#100479

Merged
steipete merged 5 commits into
mainfrom
claude/interesting-cohen-e2df20
Jul 6, 2026
Merged

fix(control-ui): keep the dashboard mounted with a reconnect banner on gateway drops#100479
steipete merged 5 commits into
mainfrom
claude/interesting-cohen-e2df20

Conversation

@steipete

@steipete steipete commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Once the Control UI has an authenticated session, any WebSocket drop (gateway restart, laptop sleep, SSH-tunnel churn in the macOS app's remote mode, network blip) unmounts the entire dashboard and replaces it with the login gate showing a red "Could not connect" card — even though GatewayBrowserClient keeps auto-reconnecting with backoff underneath. The operator is visually "logged out" by a transient transport failure and loses all page context; when the retry succeeds the gate flashes back to the dashboard.

The disconnect-resilient machinery already existed but was unreachable: chat-page.applyGatewaySnapshot handles in-place disconnect/reconnect (queued sends marked "waiting for reconnect", history re-sync on reconnect) and the sidebar has an offline status dot — none of it ever visible because OpenClawApp.render swapped the shell for the gate the moment connected flipped false.

Fixes #100475.

Why This Change Was Made

Failing gracefully requires distinguishing "transport interrupted, client is retrying" from "credentials rejected, operator input needed". That fact lives in the client's close handler, so it is now exported instead of re-derived:

  • ui/src/api/gateway.ts: the close path computes willRetry once and uses it both to schedule the reconnect and to report it in onClose — a single source of truth for retry policy. Constructor failures (bad URL, mixed content) report willRetry: false.
  • ui/src/app/gateway-store.ts (extracted from bootstrap.ts for a test seam, with an injectable client factory): the snapshot gains reconnecting = everConnected && willRetry, where everConnected tracks whether a hello succeeded this page lifetime. First-connect failures stay on the gate; established sessions degrade to reconnecting.
  • ui/src/app/app-host.ts: the shell stays mounted while reconnecting, rendering an amber "Gateway connection lost — reconnecting…" banner (new openclaw-connection-banner) with a Retry now action. The login gate is reserved for first connects, credential rejections, and manual gate submissions (a loginGatePinned flag prevents shell flicker while a gate-submitted attempt is in flight).
  • Event-gap recovery (onGap → fresh client) now also keeps the shell mounted instead of flashing the gate.
  • .callout.warn CSS variant added — previously referenced by update status banners but never styled.

Keeping the shell mounted makes the existing per-page reconnect handling load-bearing (chat re-syncs subscriptions/queued sends on reconnect; the shell's ensureAgentsList already re-fetches on client change).

User Impact

  • Transient gateway drops no longer kick operators to the login form; the dashboard stays up with a status banner and recovers in place.
  • Real credential problems (bad token/password, revoked pairing, rate limit) still land on the gate with the existing diagnostic cards.
  • macOS app remote mode: a dead SSH tunnel now shows the dashboard + banner instead of a dead-end login form (the stale-tunnel-port endpoint refresh in the app is tracked separately in [Bug]: macOS Dashboard window keeps a dead SSH tunnel port after tunnel restart (remote mode) #100476).
  • New i18n strings under connection.*, translated for all locales via pnpm ui:i18n:sync (0 fallbacks).
  • Docs: docs/web/control-ui.md gains a "Connection loss and reconnect" section.

Evidence

  • New ui/src/app/gateway-store.test.ts (7 tests): first-connect failures stay on the gate; transport drop after hello → reconnecting: true; credential rejection → gate; event-gap recovery keeps reconnecting; stop() resets lineage; superseded clients cannot demote the snapshot.
  • ui/src/api/gateway.node.test.ts: willRetry asserted on recoverable close, credential rejection (AUTH_PASSWORD_MISMATCH), both token-mismatch no-retry paths, and both constructor-failure paths.
  • pnpm check:changed green on Blacksmith Testbox (run: https://github.com/openclaw/openclaw/actions/runs/28756286250).
  • Local focused runs: vitest run --config test/vitest/vitest.ui.config.ts ui/src/app/gateway-store.test.ts ui/src/api/gateway.node.test.ts ui/src/pages/skill-workshop/proposals.test.ts → green. (ui/src/pages/plugin/plugin-page.test.ts fails identically on pristine HEAD in this macOS environment and is green on the Linux Testbox run above — pre-existing local flake, unrelated.)

@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 5, 2026, 8:36 PM ET / 00:36 UTC.

Summary
The branch adds a Control UI reconnecting gateway state, keeps the shell mounted with a reconnect banner after recoverable post-auth Gateway drops, and updates focused tests, docs, CSS, and generated i18n output.

Reproducibility: yes. source-reproducible: current main sets connected=false on Gateway close while the client can still schedule reconnect, and the app host renders the login gate for every disconnected state. I did not run a live browser/Gateway restart in this read-only review.

Review metrics: 1 noteworthy metric.

  • App Snapshot Contract: 1 internal state field added. The new reconnecting field is the contract that keeps app-host, shell, and page-level reconnect handling in sync.

Stored data model
Persistent data-model change detected: database schema: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatTranscriptCache.swift, migration/backfill/repair: packages/gateway-protocol/src/connect-error-details.test.ts, persistent cache schema: CHANGELOG.md, persistent cache schema: apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatTranscriptCacheStoreTests.swift, persistent cache schema: docs/platforms/ios.md, persistent cache schema: ui/src/i18n/.i18n/pl.tm.jsonl, and 10 more. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #100475
Summary: This PR is the active candidate fix for the current Control UI WebSocket-drop login-gate report, with an older same-root tracker and closed-unmerged prior attempts as context.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🐚 platinum hermit
Result: blocked until real behavior proof from a real setup is added.

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

Rank-up moves:

  • [P1] Add redacted browser or Playwright/Crabbox proof showing an authenticated Control UI survives a Gateway restart/drop with the reconnect banner and recovers in place.
  • Include the explicit credential-failure path returning to the login gate, with private endpoints and credentials redacted.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body supplies focused tests and CI only; no redacted browser/Gateway-drop screenshot, recording, terminal/live output, logs, or artifact shows the reconnect banner and auth-failure gate after the fix. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Mantis proof suggestion
A web UI chat proof would materially help because the missing evidence is visible Control UI behavior across a reconnect, not a code repair task. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis web ui chat proof: verify that an authenticated Control UI chat stays mounted with a reconnect banner through a Gateway WebSocket drop and recovers in place.

Risk before merge

  • [P1] The PR changes auth/session-state rendering during Gateway disconnects, and the body still lacks real browser proof that a live Gateway drop shows the banner, recovers in place, and explicit credential failures still return to the login gate.

Maintainer options:

  1. Decide the mitigation before merge
    Land this after exact-head CI is complete and the PR body includes redacted real browser proof for both transient reconnect and explicit credential-failure behavior.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P1] No automated repair is indicated; the next merge-relevant action is contributor or maintainer real-behavior proof plus normal protected PR review.

Security
Cleared: No concrete security or supply-chain concern was found; the unique Control UI diff adds no dependencies, workflows, package resolution changes, or broader secret handling.

Review details

Best possible solution:

Land this after exact-head CI is complete and the PR body includes redacted real browser proof for both transient reconnect and explicit credential-failure behavior.

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

Yes, source-reproducible: current main sets connected=false on Gateway close while the client can still schedule reconnect, and the app host renders the login gate for every disconnected state. I did not run a live browser/Gateway restart in this read-only review.

Is this the best way to solve the issue?

Yes, this appears to be the best code shape: carry the Gateway client's retry decision into the app snapshot, keep the shell only for post-hello recoverable drops, and return to the gate for first-connect or credential failures. The remaining gap is real behavior proof, not a different implementation layer.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P1: The PR targets an active Control UI workflow where authenticated operators are kicked out of the dashboard on routine Gateway WebSocket drops.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🐚 platinum hermit.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body supplies focused tests and CI only; no redacted browser/Gateway-drop screenshot, recording, terminal/live output, logs, or artifact shows the reconnect banner and auth-failure gate after the fix. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

What I checked:

  • Current main renders the login gate for every disconnected app snapshot: OpenClawApp.render on current main returns <openclaw-login-gate> whenever gatewayConnected is false, before shell routes can show their offline behavior. (ui/src/app/app-host.ts:232, 7e7fc0075e3c)
  • Current main demotes recoverable closes to disconnected: The app gateway close callback publishes connected: false and clears hello without carrying whether the browser client is still retrying. (ui/src/app/bootstrap.ts:348, 7e7fc0075e3c)
  • Current main already retries recoverable closes: The Gateway browser client notifies close, then schedules reconnect for non-auth recoverable close paths; the bug is the UI state rendered while retrying. (ui/src/api/gateway.ts:581, 7e7fc0075e3c)
  • PR uses the client retry decision as the UI contract: The PR adds willRetry to onClose and computes it from the same branch that schedules reconnect, avoiding a separate app-host re-parse of auth errors. (ui/src/api/gateway.ts:301, b6ae0d502b41)
  • PR keeps only established-session recoverable drops in reconnecting state: createApplicationGateway latches everConnected after hello and sets reconnecting: everConnected && willRetry; first-connect failures and credential rejections remain non-reconnecting. (ui/src/app/gateway-store.ts:150, b6ae0d502b41)
  • PR keeps the shell mounted and renders the reconnect banner: The host renders the login gate only when disconnected and not reconnecting, and the mounted shell renders <openclaw-connection-banner> while disconnected. (ui/src/app/app-host.ts:245, b6ae0d502b41)

Likely related people:

  • steipete: Authored this PR and several recent merged Control UI app-host/bootstrap/gateway-adjacent changes, including sidebar and logbook work that touched the mounted shell path. (role: recent area contributor; confidence: high; commits: 51771c3a1485, c730d8f1f1bb, 9c7848928f8b; files: ui/src/app/app-host.ts, ui/src/app/bootstrap.ts, ui/src/api/gateway.ts)
  • shakkernerd: GitHub path history shows the current Control UI architecture refactor that introduced the current ui/src/app/* app-host/bootstrap structure this PR modifies. (role: architecture refactor author; confidence: high; commits: 65e12328aa20; files: ui/src/app/app-host.ts, ui/src/app/bootstrap.ts, ui/src/api/gateway.ts)
  • zhangguiping-xydt: Path history for the older gateway UI file shows prior merged reconnect/session persistence work in the same behavior family. (role: adjacent reconnect contributor; confidence: medium; commits: 2dc2d73b07dd; files: ui/src/ui/app-gateway.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 (3 earlier review cycles)
  • reviewed 2026-07-05T22:38:23.827Z sha f676e59 :: needs real behavior proof before merge. :: [P2] Update all snapshot fixtures for reconnecting | [P2] Regenerate the docs map
  • reviewed 2026-07-05T22:54:14.903Z sha c26abd2 :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-06T00:15:00.969Z sha ad73fc0 :: needs real behavior proof before merge. :: none

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. labels Jul 5, 2026
@steipete
steipete force-pushed the claude/interesting-cohen-e2df20 branch from c26abd2 to a7be62e Compare July 5, 2026 23:50
@openclaw-barnacle openclaw-barnacle Bot added app: android App: android app: ios App: ios gateway Gateway runtime scripts Repository scripts labels Jul 6, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 6, 2026
@openclaw-barnacle openclaw-barnacle Bot added the agents Agent runtime and tooling label Jul 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6ae0d502b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CHANGELOG.md

### Changes

- **iOS offline chat:** pre-paint recent sessions and canonical transcripts from a protected, bounded per-gateway cache, keep sending disabled offline, and purge cached conversation text when pairing is reset. (#100194)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove hand-edited changelog entries

For normal commits like this one, the repo instructions say CHANGELOG.md is release-only and release generation derives entries from merged PRs/direct commits. Adding Unreleased bullets here means the generated release changelog can be overwritten or conflict with hand-authored text, and this commit also adds release-note content outside the control-ui fix it claims to make. Please keep release-note context in the PR/commit body and leave CHANGELOG.md to the release generator.

Useful? React with 👍 / 👎.

steipete added 4 commits July 5, 2026 18:33
…n gateway drops

Once a session is established, a dropped gateway WebSocket no longer
unmounts the dashboard into the login gate. The client's close handler
now reports willRetry (the same fact that drives its reconnect
scheduling), the gateway store derives a `reconnecting` snapshot state
from it (everConnected && willRetry), and the app shell stays mounted
with an amber "Gateway connection lost - reconnecting" banner plus a
Retry now action while the client retries with backoff. The login gate
is reserved for first connects, credential rejections, and manual gate
submissions; event-gap recovery also no longer flashes the gate.

createApplicationGateway moved from bootstrap.ts to gateway-store.ts
with an injectable client factory for direct behavior tests. Adds the
previously unstyled `.callout.warn` variant and a "Connection loss and
reconnect" docs section.

Fixes #100475
@steipete
steipete force-pushed the claude/interesting-cohen-e2df20 branch from b6ae0d5 to 211240c Compare July 6, 2026 01:35
@openclaw-barnacle openclaw-barnacle Bot removed app: android App: android app: ios App: ios gateway Gateway runtime scripts Repository scripts agents Agent runtime and tooling labels Jul 6, 2026
@steipete
steipete merged commit e596e28 into main Jul 6, 2026
71 checks passed
@steipete
steipete deleted the claude/interesting-cohen-e2df20 branch July 6, 2026 01:55
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 6, 2026
…n gateway drops (openclaw#100479)

* fix(control-ui): keep the dashboard mounted with a reconnect banner on gateway drops

Once a session is established, a dropped gateway WebSocket no longer
unmounts the dashboard into the login gate. The client's close handler
now reports willRetry (the same fact that drives its reconnect
scheduling), the gateway store derives a `reconnecting` snapshot state
from it (everConnected && willRetry), and the app shell stays mounted
with an amber "Gateway connection lost - reconnecting" banner plus a
Retry now action while the client retries with backoff. The login gate
is reserved for first connects, credential rejections, and manual gate
submissions; event-gap recovery also no longer flashes the gate.

createApplicationGateway moved from bootstrap.ts to gateway-store.ts
with an injectable client factory for direct behavior tests. Adds the
previously unstyled `.callout.warn` variant and a "Connection loss and
reconnect" docs section.

Fixes openclaw#100475

* chore(i18n): regenerate control-ui locale bundles for connection banner strings

* chore(control-ui): unbreak CI - add reconnecting to overlays snapshot fixture, regenerate docs map

* chore(i18n): re-sync locale metadata after rebase onto refreshed main locales

* chore(i18n): refresh raw-copy baseline after rebase
giodl73-repo pushed a commit to giodl73-repo/openclaw that referenced this pull request Jul 8, 2026
…n gateway drops (openclaw#100479)

* fix(control-ui): keep the dashboard mounted with a reconnect banner on gateway drops

Once a session is established, a dropped gateway WebSocket no longer
unmounts the dashboard into the login gate. The client's close handler
now reports willRetry (the same fact that drives its reconnect
scheduling), the gateway store derives a `reconnecting` snapshot state
from it (everConnected && willRetry), and the app shell stays mounted
with an amber "Gateway connection lost - reconnecting" banner plus a
Retry now action while the client retries with backoff. The login gate
is reserved for first connects, credential rejections, and manual gate
submissions; event-gap recovery also no longer flashes the gate.

createApplicationGateway moved from bootstrap.ts to gateway-store.ts
with an injectable client factory for direct behavior tests. Adds the
previously unstyled `.callout.warn` variant and a "Connection loss and
reconnect" docs section.

Fixes openclaw#100475

* chore(i18n): regenerate control-ui locale bundles for connection banner strings

* chore(control-ui): unbreak CI - add reconnecting to overlays snapshot fixture, regenerate docs map

* chore(i18n): re-sync locale metadata after rebase onto refreshed main locales

* chore(i18n): refresh raw-copy baseline after rebase
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui docs Improvements or additions to documentation maintainer Maintainer-authored PR P1 High-priority user-facing bug, regression, or broken workflow. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: XL status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Control UI kicks authenticated sessions to the login gate on any WebSocket drop instead of degrading gracefully

1 participant