Skip to content

fix: recover Control UI bundle loading after gateway restart#99111

Merged
steipete merged 1 commit into
openclaw:mainfrom
ZengWen-DT:fix/99092-control-ui-bundle-retry
Jul 6, 2026
Merged

fix: recover Control UI bundle loading after gateway restart#99111
steipete merged 1 commit into
openclaw:mainfrom
ZengWen-DT:fix/99092-control-ui-bundle-retry

Conversation

@ZengWen-DT

Copy link
Copy Markdown
Contributor

Closes #99092

What Problem This Solves

Fixes an issue where users who load the Control UI while the gateway is restarting can land on the static "Control UI did not start" fallback and stay there even after the gateway becomes healthy again.

Before this change, the Keep waiting button only hid the fallback and rearmed the watchdog. It did not retry the app bundle, so a failed initial module fetch still required a full page reload.

Why This Change Was Made

The static fallback now performs bounded app-bundle recovery before the TypeScript app has mounted: it finds the original module script, appends a cache-busted retry script when the watchdog fires or when users click Keep waiting, and probes the current same-origin page with HEAD so the fallback can distinguish a restarting/unreachable gateway from a reachable gateway where module execution is still blocked.

This keeps the fix inside the pre-mount Control UI fallback. It intentionally does not change launchd restart behavior or add new gateway config surface.

AI-assisted.

User Impact

Users no longer need to manually reload the Control UI when the only problem was a transient app-bundle fetch failure during gateway restart. The fallback copy now points at gateway restart/reachability first, while still preserving the clean-profile guidance for persistent module blocking.

Evidence

Focused tests and checks:

  • node scripts/run-vitest.mjs ui/src/ui/mount-fallback.test.ts passed: 1 file, 5 tests.
  • node scripts/run-vitest.mjs src/gateway/control-ui-csp.test.ts passed: gateway CSP shard, 4 files, 60 tests.
  • node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.test.ui.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/test-ui.tsbuildinfo passed.
  • node_modules/.bin/oxfmt --check --threads=1 ui/index.html ui/src/ui/mount-fallback.test.ts passed.
  • pnpm ui:build passed. Vite still reports the existing large main chunk warning.

Real browser proof against the final built Control UI:

  • Served dist/control-ui/index.html from a local HTTP server.
  • Forced the first built main bundle request to return 503 to simulate the gateway restart window.
  • Allowed the cache-busted retry request to serve the real built bundle.
  • Chromium result:
{
  "mainRequests": 2,
  "initialFailures": 1,
  "retrySrcIncludesCacheBust": true,
  "mounted": true,
  "fallbackHidden": true,
  "navTypes": ["navigate"]
}

Local provider smoke requested for this issue:

  • pnpm openclaw agent --local --model deepseek/deepseek-v4-pro --session-key agent:default:ui-retry-proof --message "Reply exactly: OPENCLAW_UI_RETRY_PROOF_OK" --json --timeout 120 returned OPENCLAW_UI_RETRY_PROOF_OK with provider deepseek, model deepseek-v4-pro.

@clawsweeper

clawsweeper Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 6, 2026, 12:16 AM ET / 04:16 UTC.

Summary
The PR changes the static Control UI mount fallback to retry a cache-busted current document, perform one bounded recovery navigation, update fallback copy, and add unit plus Chromium E2E coverage.

PR surface: Source +73, Tests +117. Total +190 across 3 files.

Reproducibility: yes. at source level: current main's Keep waiting handler only hides the fallback and rearms the same timer after the app module fails to register. I did not rerun the macOS launchd scenario locally, but the linked issue and browser proof cover the central failed-module recovery path.

Review metrics: none identified.

Stored data model
Persistent data-model change detected: persistent cache schema: ui/src/app/mount-fallback.test.ts. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #99092
Summary: This PR is the viable candidate fix for the linked Control UI gateway-restart and stale-asset mount fallback issue; an older issue shares the fallback surface with a distinct old-iOS/proxy trigger, and the competing PR is not a safe canonical replacement.

Members:

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

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

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

Rank-up moves:

  • none.

Next step before merge

  • No automated repair is needed; the proof-positive head has no concrete review finding, so the remaining action is maintainer merge review and normal exact-head CI gating.

Security
Cleared: The diff changes same-origin Control UI fallback code and tests only, adds no dependencies/workflows/secrets surface, and relies on existing Gateway CSP hash recomputation.

Review details

Best possible solution:

Land the refreshed static fallback recovery after ordinary exact-head maintainer mergeability and CI gates, while keeping old-iOS/proxy compatibility and launchd/config changes separate.

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

Yes, at source level: current main's Keep waiting handler only hides the fallback and rearms the same timer after the app module fails to register. I did not rerun the macOS launchd scenario locally, but the linked issue and browser proof cover the central failed-module recovery path.

Is this the best way to solve the issue?

Yes. Fetching a cache-busted current document before one bounded recovery navigation is the narrowest maintainable fix here because it avoids duplicate module-graph execution and does not add launchd or gateway config surface.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 37f96bde4d90.

Label changes

Label justifications:

  • P2: The PR fixes a limited-blast-radius Control UI startup recovery bug with a manual reload workaround and focused web UI fallback scope.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (linked_artifact): The PR body and maintainer refresh comment include after-fix Chromium/Testbox proof showing initial module failure, fresh-document recovery, mounted chat welcome screen, and recovery-marker removal with inspected screenshot/video artifacts.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body and maintainer refresh comment include after-fix Chromium/Testbox proof showing initial module failure, fresh-document recovery, mounted chat welcome screen, and recovery-marker removal with inspected screenshot/video artifacts.
Evidence reviewed

PR surface:

Source +73, Tests +117. Total +190 across 3 files.

View PR surface stats
Area Files Added Removed Net
Source 1 79 6 +73
Tests 2 118 1 +117
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 3 197 7 +190

What I checked:

  • Current main fallback is still one-shot: On current main, the Keep waiting handler only hides the fallback and rearms the timer, so a failed app module is not fetched or executed again. (ui/index.html:311, 37f96bde4d90)
  • Latest release still lacks recovery: The v2026.6.11 index.html has the same extension-focused fallback copy and Keep waiting hide/rearm behavior, so the proposed fix is not already shipped. (ui/index.html:311, e085fa1a3ffd)
  • PR head implements bounded fresh-document recovery: The PR head detects a recovery marker, fetches the current same-origin document with a cache-busting marker, performs one location.replace on success, bounds retries to six attempts, and removes the marker from the visible URL. (ui/index.html:263, cd2979e3f727)
  • Unit tests cover retry probing and retry bounds: The PR adds mount-fallback tests that assert a cache-busted current-document fetch is attempted and unavailable-gateway recovery attempts stop after six tries. (ui/src/app/mount-fallback.test.ts:119, cd2979e3f727)
  • Chromium E2E covers the recovery path: The new E2E test forces the first app module request to return 503, waits for the app shell and chat welcome screen, checks exactly two document loads, and verifies the recovery marker leaves the URL. (ui/src/e2e/mount-recovery.e2e.test.ts:46, cd2979e3f727)
  • CSP boundary stays same-origin: Gateway serving recomputes inline script hashes for the prepared Control UI document, and the existing CSP connect-src includes same-origin fetches used by the recovery probe. (src/gateway/control-ui.ts:763, 37f96bde4d90)

Likely related people:

  • BunsDev: GitHub commit metadata for 42fc84f shows BunsDev authored the static Control UI mount fallback, docs, and tests that define the affected pre-mount behavior. (role: introduced fallback surface; confidence: high; commits: 42fc84f4b4c6; files: ui/index.html, ui/src/app/mount-fallback.test.ts, docs/web/control-ui.md)
  • steipete: History shows steipete introduced the browser Control UI/Gateway serving boundary and authored the current refreshed recovery commit on this PR. (role: original Control UI/Gateway serving author and current refresh owner; confidence: high; commits: df0c51a63bf5, cd2979e3f727; files: src/gateway/control-ui.ts, ui/index.html, ui/src/app/app-host.ts)
  • Alix-007: Recent merged work changed Control UI bootstrap config and base-path serving, adjacent to built asset loading and restart/version-skew recovery behavior. (role: recent adjacent contributor; confidence: medium; commits: f5dd33c975e5; files: src/gateway/control-ui.ts, ui/vite.config.ts, docs/web/control-ui.md)
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-03T01:41:37.864Z sha 18f0178 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-06T02:46:56.146Z sha d7cc641 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-06T03:29:27.071Z sha e45c6f9 :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. labels Jul 2, 2026
@ZengWen-DT
ZengWen-DT force-pushed the fix/99092-control-ui-bundle-retry branch from 06c6b5e to 18f0178 Compare July 3, 2026 01:24
@steipete steipete self-assigned this Jul 6, 2026
@steipete
steipete force-pushed the fix/99092-control-ui-bundle-retry branch from 18f0178 to d7cc641 Compare July 6, 2026 02:35
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Maintainer refresh complete at d7cc6414cdb.

What changed:

  • replaced repeated cache-busted module injection and HEAD probing with one canonical recovery path: fetch a cache-busted current document, then replace the unmounted page once the Gateway is reachable;
  • this handles both transient restart failures and stale app/Gateway asset hashes while avoiding duplicate module-graph execution;
  • bounded recovery to six probes and one fresh-page navigation; a marker prevents reload loops and is removed before the app reads the URL;
  • kept manual retry/troubleshooting behavior for persistent failures and updated the misleading extension-only copy;
  • added focused fallback/CSP coverage plus a Chromium end-to-end failure→fresh-document→mounted-chat test with screenshot/video proof;
  • preserved contributor credit in the replacement commit.

Verification:

  • node scripts/run-vitest.mjs ui/src/app/mount-fallback.test.ts src/gateway/control-ui-csp.test.ts — 76 assertions passed across the UI/CSP projects
  • pnpm ui:build
  • node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner ui/src/e2e/mount-recovery.e2e.test.ts
  • pnpm check:changed -- ui/index.html ui/src/app/mount-fallback.test.ts ui/src/e2e/mount-recovery.e2e.test.ts
  • fresh branch autoreview: no accepted/actionable findings

Remote proof: Blacksmith Testbox through Crabbox, tbx_01kwtkvv7h6zpczqd5ybf1bjjn (tidal-prawn), Actions run https://github.com/openclaw/openclaw/actions/runs/28763809658. The live Chromium run deliberately returned 503 for the first app module, observed exactly two document loads, mounted the full chat welcome screen, and removed the recovery marker. Screenshot and video artifacts were visually inspected.

The browser proof caught and led to a fix for an initial reload-loop bug in the marker guard before this refresh was pushed.

Thanks @ZengWen-DT for the original diagnosis and recovery direction.

Exact-head land-ready update: cd2979e is the clean current-main-plus-reviewed-patch head. Hosted CI run https://github.com/openclaw/openclaw/actions/runs/28767171169 passed at that exact SHA. The rebase changed ancestry only; the tested diff and Testbox/browser/PTY proof above are unchanged. Known proof gaps: none.

@steipete
steipete force-pushed the fix/99092-control-ui-bundle-retry branch 2 times, most recently from e45c6f9 to b1f20f8 Compare July 6, 2026 03:46
@openclaw-barnacle openclaw-barnacle Bot added extensions: clawrouter Extension: clawrouter size: XL and removed size: M labels Jul 6, 2026
@steipete
steipete force-pushed the fix/99092-control-ui-bundle-retry branch from 7704374 to cd2979e Compare July 6, 2026 04:10
@steipete
steipete merged commit f36f3f3 into openclaw:main Jul 6, 2026
85 of 88 checks passed
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 6, 2026
giodl73-repo pushed a commit to giodl73-repo/openclaw that referenced this pull request Jul 8, 2026
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 P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: M status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Control UI shows "Control UI did not start" during gateway restart and cannot self-recover

2 participants