openclaw - ✅(Solved) Fix "Update now" button does nothing — no feedback when update.run returns "skipped" status [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#62492Fetched 2026-04-08 03:03:33
View on GitHub
Comments
0
Participants
1
Timeline
0
Reactions
0
Participants

Error Message

Clicking "Update now" in the GUI update notification banner ("Update available: v2026.4.5 (running v2026.4.2). Update now") does nothing visible. The button briefly disables then re-enables, with no success message, no error, and no restart. // Only shows error if ok === false if (t && t.ok === false) e.lastError = Update ${t.result?.status ?? 'error'}: ${t.result?.reason ?? 'Update failed.'}; The backend (gateway-cli) returns { ok: result.status !== "error" } — meaning status: "skipped" (e.g. dirty working tree, no upstream, not-openclaw-root) returns ok: true. The UI receives a truthy ok, sets no error, and silently exits — the user sees nothing. e.lastError = Update failed: ${t.result?.reason ?? 'unknown error'};

Root Cause

Root Cause (traced in minified source)

PR fix notes

PR #66865: fix(update): Update button must actually apply the update on all install modes

Description (problem / solution / changelog)

Initial prompt provided to the implementing agent:

fix(update): Update button must actually apply the update on all install modes

Intent

When the user clicks "Update now" in the control UI, the running openclaw gateway must end up executing the new version's code — on every supported install mode (git checkout, npm-global, pnpm-global, bun-global), on every supervisor model (systemd, launchd, unmanaged foreground), on Linux, macOS and Windows. Anything less is a lie to the user.

Goal (one invariant)

After update.run returns status: "ok", within restartDelayMs + drain + ~5s, process.versions.openclaw (or equivalent version identity) reported by the gateway's RPC/health endpoint MUST equal the version installed on disk. If that cannot be guaranteed, update.run MUST return a non-ok status with a specific reason code the UI renders verbatim.

Current behavior (root causes — verify, then fix)

  1. In-process restart does not reload modules. src/cli/gateway-cli/run-loop.ts:214-218 runs a while(true) loop on SIGUSR1 (comment: "no supervisor required"). Node's ESM/CJS module cache is per-process and never invalidated — the loop re-initializes the server but re-executes the OLD cached modules. Under npm/pnpm global installs, the on-disk files ARE replaced but the running process continues executing pre-update code until the OS process itself is replaced.

  2. Skipped/error statuses are reported as ok: true. src/gateway/server-methods/update.ts:123 sets ok: result.status !== "error", so status: "skipped" (dirty tree, no-upstream, not-git-install, not-openclaw-root) surfaces as success. UI clears state, no feedback. Matches #62492.

  3. Lazy-loaded hashed chunks 404 after package replacement. Observed in #64892: after npm i -g openclaw@latest, running process imports of dist/*-<hash>.js fail with ERR_MODULE_NOT_FOUND because the hashed names changed. Any window between "files replaced" and "process replaced" is a landmine.

  4. commands.restart=false silently swallows the restart. run-loop.ts:186-197 drops SIGUSR1 when not authorized; update.run does not surface this — the install still happened, the process did not restart.

Fix (do all of these — partial fixes don't satisfy the invariant)

A. Re-exec the process on post-update restart. Replace the in-process while-loop path with a hard self-exec when the restart reason is update.run: - spawn process.execPath with process.argv.slice(1) as detached: true, stdio: "inherit", new sid - process.exit(0) AFTER the child reports healthy on the control port (short polling, bounded). If the child fails to come healthy within N seconds, keep the parent alive and return the failure to the UI via the restart sentinel. Under systemd/launchd, skip the spawn and just exit(0) so the supervisor respawns cleanly. Detect the supervisor via the existing daemon module. Respect OPENCLAW_NO_RESPAWN semantics already in play (#65668).

B. Fix ok semantics for update.run. ok must be status === "ok". Every skipped and error path must return a stable, enumerated reason string (dirty, no-upstream, not-git-install, not-openclaw-root, deps-install-failed, build-failed, ui-build-failed, global-install-failed, restart-disabled, restart-unhealthy, ...).

C. Surface all reasons in the UI. Drop the ok === false-only feedback branch in the control-ui update handler. Render a banner for every non-ok outcome with the reason code, and for skipped render next-action guidance ("commit or stash changes", "set upstream", "not a git checkout — run openclaw update from CLI to do a global reinstall", etc.). Aligns with #62492.

D. Block global-install update path when the running process cannot be safely replaced. If install mode is npm/pnpm/bun global AND commands.restart=false AND no supervisor is detected, return status: "skipped", reason: "restart-unavailable" BEFORE running the package install, so we don't leave the user with new files on disk and an old process in memory.

E. Add a post-restart version assertion. After respawn, the new process writes to the restart-sentinel file with its actual package.json version. The UI polls the sentinel and verifies sentinel.after.version === expected. If it does not match, show a hard error: "Update installed but running version did not change — restart may have been blocked."

F. Tests (all required):

  • Unit: update.run returns ok: false for every skipped/error reason.
  • Integration: update.run on npm-global install under a fake supervisor → child respawn → version assertion matches.
  • Integration: update.run under foreground-unmanaged with commands.restart=false → refuses at step D before install.
  • Regression (matches #64892): lazy-loaded chunk import after update succeeds, no ERR_MODULE_NOT_FOUND.

Out of scope

  • Rewriting the git-mode update pipeline.
  • Changing update channel semantics.
  • Plugin self-update (#58352 is separate).

Reference issues to cross-link in PR

Fixes #62492. Addresses #64892, #63562. Related to #65668, #54751.

Changed files

  • src/cli/gateway-cli/run-loop.test.ts (modified, +96/-0)
  • src/cli/gateway-cli/run-loop.ts (modified, +114/-5)
  • src/gateway/method-scopes.test.ts (modified, +1/-0)
  • src/gateway/method-scopes.ts (modified, +1/-0)
  • src/gateway/server-methods-list.ts (modified, +1/-0)
  • src/gateway/server-methods/update.test.ts (modified, +134/-1)
  • src/gateway/server-methods/update.ts (modified, +40/-7)
  • src/gateway/server-restart-sentinel.ts (modified, +26/-0)
  • src/gateway/server-startup-post-attach.test.ts (modified, +6/-0)
  • src/gateway/server-startup-post-attach.ts (modified, +5/-0)
  • src/gateway/server.roles-allowlist-update.test.ts (modified, +33/-3)
  • src/infra/infra-runtime.test.ts (modified, +16/-0)
  • src/infra/process-respawn.test.ts (modified, +43/-1)
  • src/infra/process-respawn.ts (modified, +54/-9)
  • src/infra/restart-sentinel.test.ts (modified, +51/-0)
  • src/infra/restart-sentinel.ts (modified, +65/-0)
  • src/infra/restart.ts (modified, +19/-7)
  • src/infra/update-runner.test.ts (modified, +2/-2)
  • src/infra/update-runner.ts (modified, +126/-11)
  • ui/src/ui/app-gateway.node.test.ts (modified, +116/-0)
  • ui/src/ui/app-gateway.ts (modified, +88/-0)
  • ui/src/ui/app-render.ts (modified, +5/-0)
  • ui/src/ui/app-settings.ts (modified, +1/-1)
  • ui/src/ui/app-view-state.ts (modified, +2/-0)
  • ui/src/ui/app.ts (modified, +2/-0)
  • ui/src/ui/controllers/agents.test.ts (modified, +2/-0)
  • ui/src/ui/controllers/config.test.ts (modified, +41/-1)
  • ui/src/ui/controllers/config.ts (modified, +47/-5)

Code Example

// Only shows error if ok === false
if (t && t.ok === false) e.lastError = `Update ${t.result?.status ?? 'error'}: ${t.result?.reason ?? 'Update failed.'}`;

---

if (t?.result?.status === 'ok') {
  // show "Updated successfully, restarting..." 
} else if (t?.result?.status === 'skipped') {
  e.lastError = `Update skipped: ${t.result?.reason ?? 'unknown reason'}`;
} else if (t?.ok === false) {
  e.lastError = `Update failed: ${t.result?.reason ?? 'unknown error'}`;
}
RAW_BUFFERClick to expand / collapse

What Happened

Clicking "Update now" in the GUI update notification banner ("Update available: v2026.4.5 (running v2026.4.2). Update now") does nothing visible. The button briefly disables then re-enables, with no success message, no error, and no restart.

Expected Behavior

After clicking "Update now":

  • A loading/progress state should be visible while the update runs
  • On success: a confirmation message (e.g. "Updated to v2026.4.5, restarting...")
  • On failure or skip: a clear message explaining why the update didn't happen (e.g. "Update skipped: no upstream configured" or "Already up to date")

Root Cause (traced in minified source)

The frontend handler (ar(e) in control-ui/assets/index-*.js) only surfaces feedback in two cases:

// Only shows error if ok === false
if (t && t.ok === false) e.lastError = `Update ${t.result?.status ?? 'error'}: ${t.result?.reason ?? 'Update failed.'}`;

The backend (gateway-cli) returns { ok: result.status !== "error" } — meaning status: "skipped" (e.g. dirty working tree, no upstream, not-openclaw-root) returns ok: true. The UI receives a truthy ok, sets no error, and silently exits — the user sees nothing.

runGatewayUpdate (in server-startup-matrix-migration-*.js) has multiple early-return "skipped" paths:

  • reason: "dirty" — working tree has uncommitted changes
  • reason: "no-upstream" — git branch has no upstream configured
  • reason: "not-openclaw-root" — git root doesn't match package root

All of these return ok: true to the frontend with no UI feedback.

Steps to Reproduce

  1. Run openclaw v2026.4.2 with v2026.4.5 available
  2. Open the GUI — see "Update available: v2026.4.5 (running v2026.4.2). Update now"
  3. Click "Update now"
  4. Observe: button briefly disables and re-enables, nothing else happens

Environment

  • openclaw version: 2026.4.2
  • install method: global npm (/usr/lib/node_modules/openclaw)
  • OS: Linux 6.8.12-20-pve

Suggested Fix

In the frontend update handler, also check result.status (not just ok) and surface a message for all non-success outcomes:

if (t?.result?.status === 'ok') {
  // show "Updated successfully, restarting..." 
} else if (t?.result?.status === 'skipped') {
  e.lastError = `Update skipped: ${t.result?.reason ?? 'unknown reason'}`;
} else if (t?.ok === false) {
  e.lastError = `Update failed: ${t.result?.reason ?? 'unknown error'}`;
}

Alternatively, always show a status toast/banner for the full range of update.run result statuses.

extent analysis

TL;DR

Modify the frontend update handler to check result.status and surface messages for non-success outcomes, including "skipped" updates.

Guidance

  • Check the result.status in the frontend update handler to handle different update outcomes.
  • Surface a message for "skipped" updates, including the reason for skipping, to provide user feedback.
  • Consider showing a status toast/banner for all possible update.run result statuses to improve user experience.
  • Review the runGatewayUpdate function to ensure it returns relevant information for the frontend to handle different update scenarios.

Example

if (t?.result?.status === 'ok') {
  // show "Updated successfully, restarting..." 
} else if (t?.result?.status === 'skipped') {
  e.lastError = `Update skipped: ${t.result?.reason ?? 'unknown reason'}`;
} else if (t?.ok === false) {
  e.lastError = `Update failed: ${t.result?.reason ?? 'unknown error'}`;
}

Notes

The suggested fix focuses on improving user feedback for non-success update outcomes. However, it may not address underlying issues that cause updates to be skipped. Additional debugging or logging may be necessary to identify and resolve these issues.

Recommendation

Apply the suggested workaround to modify the frontend update handler and improve user feedback for non-success update outcomes. This change will provide more informative error messages and enhance the overall 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…

Still need to ship something?

×6

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

Back to top recommendations

TRENDING