Skip to content

fix(control-ui): retry bundle fetch and improve restart diagnosis (#99092)#99172

Closed
jtcole wants to merge 1 commit into
openclaw:mainfrom
jtcole:fix/control-ui-restart-recovery
Closed

fix(control-ui): retry bundle fetch and improve restart diagnosis (#99092)#99172
jtcole wants to merge 1 commit into
openclaw:mainfrom
jtcole:fix/control-ui-restart-recovery

Conversation

@jtcole

@jtcole jtcole commented Jul 2, 2026

Copy link
Copy Markdown

Summary

When the gateway restarts, the Control UI's JS bundle fails to load. The "Keep waiting" button only re-armed a 12s timer without retrying the fetch. The error message blamed browser extensions instead of "gateway is restarting." Only a full page reload could fix it.

Changes

ui/index.html — inline fallback script

  1. Gateway reachability probe: On fallback show, fetches window.location.href to distinguish "gateway restarting" from "module blocked by extension"
  2. "Keep waiting" now retries: Re-fetches the JS bundle (/src/main.ts) with exponential backoff — 5 retries at 2s, 4s, 8s, 16s, 32s — instead of just re-arming the timer
  3. Branched error message:
    • Gateway unreachable → "Control UI is starting up" with restart guidance
    • Gateway reachable → original "Control UI did not start" with extension guidance
  4. Retry status indicator: Shows "Retrying (attempt N of 5)…" / "Gateway still unavailable. Next retry in Xs…" below the action buttons
  5. "Try again" (full page reload) preserved as manual escape hatch

ui/src/ui/mount-fallback.test.ts

  • Original test updated to install fetch mock for probe compatibility
  • New: verifies bundle is re-fetched when "Keep waiting" is clicked
  • New: verifies "starting up" message when gateway probe is unreachable
  • New: verifies "did not start" message when gateway is reachable
  • New: verifies at least one bundle fetch attempt during retry sequence

Test

node scripts/run-vitest.mjs ui/src/ui/mount-fallback.test.ts

All 6 tests pass.

Fixes #99092

…9092)

When the gateway restarts, the Control UI's JS bundle fails to load.
The 'Keep waiting' button only re-armed a 12s timer without retrying
the fetch, and the error message blamed browser extensions instead of
'gateway is restarting.' Only a full page reload could fix it.

Changes to ui/index.html inline fallback script:
- Add gateway reachability probe (fetch location.href) to distinguish
  'gateway restarting' from 'module blocked by extension'
- Make 'Keep waiting' re-fetch the JS bundle with exponential backoff
  (5 retries: 2s, 4s, 8s, 16s, 32s) instead of just re-arming the timer
- Branch the fallback dialog text based on probe result:
  - Unreachable gateway: 'Control UI is starting up' with restart guidance
  - Reachable gateway: original 'Control UI did not start' with extension guidance
- Add retry status indicator below the action buttons
- Keep 'Try again' (full reload) as a manual escape hatch
- Reset probe state on each retry sequence so re-probe works

Tests (ui/src/ui/mount-fallback.test.ts):
- Original test updated to install fetch mock for probe compatibility
- New: re-fetches bundle when 'Keep waiting' is clicked
- New: shows 'starting up' message when gateway probe is unreachable
- New: shows 'did not start' message when gateway is reachable
- New: attempts bundle fetch at least once during retry sequence

Fixes #99092
@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui size: M triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jul 2, 2026
@clawsweeper

clawsweeper Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 2, 2026, 1:05 PM ET / 17:05 UTC.

Summary
This PR changes the Control UI static mount fallback to probe gateway reachability, retry a bundle fetch from Keep waiting, update fallback copy/status, and add Vitest coverage.

PR surface: Source +158, Tests +234. Total +392 across 2 files.

Reproducibility: yes. Source inspection shows current main's Keep waiting only re-arms the fallback timer, and the PR's new retry only fetches a URL without executing the module that defines openclaw-app.

Review metrics: none identified.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #99092
Summary: This PR is the linked candidate fix for the open Control UI fallback recovery issue, but it is not yet a correct fix.

Members:

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

Merge readiness
Overall: 🧂 unranked krab
Proof: 🧂 unranked krab
Patch quality: 🧂 unranked krab
Result: blocked until real behavior proof is added.

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

Rank-up moves:

  • Replace the plain fetch retry with a path that loads and executes the actual built module script used by the page.
  • [P2] Fix the check-test-types failure in ui/src/ui/mount-fallback.test.ts.
  • [P1] Add redacted real behavior proof showing a built Control UI recovering after an initial app-bundle fetch failure without a full page reload.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body only lists a unit test command, and the proof workflow reports no authored Evidence section or after-fix built Control UI proof. 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 visible browser proof would materially help because the bug is a pre-app-mount Control UI recovery flow that unit tests do not prove. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis visual task: verify the built Control UI Keep waiting path recovers after an initial app bundle fetch failure during a gateway restart without a full reload.

Risk before merge

  • [P1] Merging as-is can leave the Control UI with a claimed automatic retry path that never executes the app bundle after an initial module fetch failure.
  • [P1] The external PR has no after-fix real browser proof for a built Control UI restart or bundle-failure recovery path.
  • [P1] The current CI type gate is failing on the new test helper casts.

Maintainer options:

  1. Fix retry execution before merge (recommended)
    Change the fallback to load the actual module script URL served by the page, add a regression that proves openclaw-app registers after retry, and fix the test type failure.
  2. Require built-browser proof
    Have the contributor or a maintainer proof lane show the built Control UI recovering from an initial bundle fetch failure without a full page reload before merge.
  3. Keep the issue as canonical
    If the branch is not revised, close or pause this PR and keep Control UI shows "Control UI did not start" during gateway restart and cannot self-recover #99092 as the canonical fix target.

Next step before merge

  • [P1] The PR needs author or maintainer revision plus external real behavior proof; repair automation should not be requested while the contributor proof gate is missing.

Security
Cleared: The diff changes same-origin Control UI fallback JavaScript and tests; I found no concrete security or supply-chain regression.

Review findings

  • [P1] Load the module instead of only fetching it — ui/index.html:365
  • [P2] Fix the test-window cast before merge — ui/src/ui/mount-fallback.test.ts:94
  • [P3] Keep retry status visible while retrying — ui/index.html:433
Review details

Best possible solution:

Revise this PR so Keep waiting loads the actual built module script and proves recovery in a real built Control UI, while keeping the linked issue open until a corrected fix lands.

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

Yes. Source inspection shows current main's Keep waiting only re-arms the fallback timer, and the PR's new retry only fetches a URL without executing the module that defines openclaw-app.

Is this the best way to solve the issue?

No. The narrow fallback-recovery direction is right, but this implementation should load the actual built module script and prove real recovery instead of only fetching /src/main.ts.

Full review comments:

  • [P1] Load the module instead of only fetching it — ui/index.html:365
    fetch("/src/main.ts") only downloads a dev entrypoint; in the packaged Control UI the served app is a built asset under dist/control-ui, and a fetch response does not execute a module or register openclaw-app. After a transient initial module failure this can report retries while never mounting the app, so Keep waiting remains a dead end.
    Confidence: 0.94
  • [P2] Fix the test-window cast before merge — ui/src/ui/mount-fallback.test.ts:94
    CI's check-test-types job fails on this cast, and on the same cast in the second restore helper, because TestWindow does not overlap Record<string, unknown>. Use a narrower deletion helper or cast through unknown so the PR can pass the repository's test type gate.
    Confidence: 0.95
  • [P3] Keep retry status visible while retrying — ui/index.html:433
    startRetrySequence() hides the fallback section before performRetry() writes status into an element inside that hidden section, so users will not see the new Retrying or backoff messages until the safety timer shows the panel again. Keep the panel visible during retry or render status somewhere that is not hidden.
    Confidence: 0.86

Overall correctness: patch is incorrect
Overall confidence: 0.93

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 3f289fdb4042.

Label changes

Label justifications:

  • P2: The PR addresses a limited-blast-radius Control UI startup recovery bug, but it is currently blocked by correctness and proof gaps rather than an urgent production outage.
  • merge-risk: 🚨 availability: The diff changes the Control UI startup fallback path and can leave users with a non-recovering automatic retry flow during gateway restart windows.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🧂 unranked krab.
  • 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 only lists a unit test command, and the proof workflow reports no authored Evidence section or after-fix built Control UI proof. 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

PR surface:

Source +158, Tests +234. Total +392 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 162 4 +158
Tests 1 235 1 +234
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 397 5 +392

What I checked:

  • Root and scoped policy read: Read the root AGENTS.md and ui/AGENTS.md; the root review-depth and UI ownership guidance apply, while the UI scoped i18n guidance is not directly implicated by this diff. (AGENTS.md:1, 3f289fdb4042)
  • Current main fallback dead-end: Current main's Keep waiting handler hides the fallback and re-arms the same watchdog without retrying or reloading the module, matching the linked issue's source-level reproduction. (ui/index.html:312, 3f289fdb4042)
  • PR retry only fetches a URL: The PR adds fetchAppBundle() that fetches /src/main.ts and returns res.ok; it does not create or execute a module script, so it cannot register openclaw-app after the initial module load failed. (ui/index.html:365, 0c5b0bde46ee)
  • Custom element registration requires module evaluation: openclaw-app is defined only when ui/src/ui/app.ts evaluates through the app module path, so a plain fetch response is insufficient for recovery. (ui/src/ui/app.ts:1681, 3f289fdb4042)
  • Packaged Control UI serves built static files: Docs say the Gateway serves static files from dist/control-ui, and the Gateway returns 404 for missing known static asset extensions; a packaged /src/main.ts request is not the built app bundle path. Public docs: docs/web/control-ui.md. (docs/web/control-ui.md:444, 3f289fdb4042)
  • CI type gate failure: The check-test-types job fails on the new TestWindow-to-Record casts at lines 94 and 122 in ui/src/ui/mount-fallback.test.ts. (ui/src/ui/mount-fallback.test.ts:94, 0c5b0bde46ee)

Likely related people:

  • BunsDev: Commit 42fc84f added the static Control UI mount fallback, docs, and mount-fallback tests that this PR changes. (role: introduced fallback surface; confidence: high; commits: 42fc84f4b4c6; files: ui/index.html, ui/src/ui/mount-fallback.test.ts, docs/web/control-ui.md)
  • steipete: Commit df0c51a introduced the browser Control UI and gateway static-serving boundary around this fallback path. (role: original Control UI and Gateway serving author; confidence: medium; commits: df0c51a63bf5; files: src/gateway/control-ui.ts, ui/src/ui/app.ts, ui/index.html)
  • Alix-007: Commit f5dd33c recently changed Control UI bootstrap config and base-path serving, which is adjacent to built asset loading and restart 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.

@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. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jul 2, 2026
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Superseded by #99111, landed as f36f3f3.

The landed fix uses one bounded canonical recovery path: probe a cache-busted current document, then replace the unmounted page once the Gateway is reachable. That recovers both transient restart failures and stale app/Gateway asset hashes without repeatedly injecting a module graph into a partially initialized page. Focused unit/CSP coverage and a real Chromium run proved first-module 503 → one fresh-document navigation → full chat mount, with no reload loop.

Thanks @jtcole for the diagnosis and proposal.

@steipete steipete closed this Jul 6, 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 merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. P2 Normal backlog priority with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: M status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence.

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