Add restart state management and error handling to self-update overlay

Add resetRestartState helper to clear restart status/detail text. Set initial "Preparing update" state in scheduleUpdate before API call. Make restart warning toast non-blocking (catch errors without await). Distinguish restart request errors from connection errors in restartAndReload - only clear overlay for actual HTTP failures, not expected connection drops. Reset restart state and remove overlay on restart request failure or

frdel committed Mar 26, 2026 at 13:10 UTC e9d9c93d130604f0acba6d31fca1eba20c41b9c2
2 files changed +35 -4
tests/test_self_update_tag_filter.py
+5
@@ -384,12 +384,17 @@ def test_self_update_frontend_uses_preloaded_select():
384 assert "response.pending || {" in content
385 assert "tag: \"\"," in content
386 assert "await this.fetchTags();" in content
387 + assert '"Preparing update"' in content
388 + assert '"Saving the request and asking Agent Zero to restart."' in content
389 assert "Release tag must use the format vX.Y." in content
390 assert "Release tag must be v1.0 or newer." in content
391 assert "isLatestSelectorTag(value)" in content
392 assert "this.isSelectableTag(this.form.tag)" in content
393 assert "getLastStatusBadgeClass(status)" in content
394 assert "this.info?.current?.display_version" in content
395 + assert "resetRestartState()" in content
396 + assert "restartRequestError" in content
397 + assert "await notificationStore.frontendWarning(" not in content
398 assert "status-pill-error" in content
399 assert "status-pill-success" in content
400 assert "this.info?.defaults?.branch ||" in content
webui/components/settings/external/self-update-store.js
+30 -4
@@ -261,6 +261,11 @@ const model = {
261 document.getElementById(`${SELF_UPDATE_OVERLAY_ID}-styles`)?.remove();
262 },
263
264 + resetRestartState() {
265 + this.restartStatusText = "";
266 + this.restartDetailText = "";
267 + },
268 +
269 setRestartState(statusText, detailText = "") {
270 this.restartStatusText = statusText;
271 this.restartDetailText = detailText;
@@ -439,6 +444,11 @@ const model = {
444
445 this.saving = true;
446 this.error = "";
447 + this.setRestartState(
448 + "Preparing update",
449 + "Saving the request and asking Agent Zero to restart."
450 + );
451 + this.ensureProgressOverlay();
452 try {
453 const response = await API.callJsonApi("self_update_schedule", {
454 branch: this.form.branch,
@@ -455,17 +465,22 @@ const model = {
465 if (this.info) {
466 this.info.pending = response.pending;
467 }
458 - await notificationStore.frontendWarning(
468 + notificationStore.frontendWarning(
469 "Agent Zero is restarting to apply the requested branch and version target.",
470 "Self Update",
471 10,
472 "self-update-restart",
473 undefined,
474 true,
465 - );
475 + ).catch((warningError) => {
476 + console.error("Failed to show self-update warning toast:", warningError);
477 + });
478 await this.restartAndReload();
479 } catch (error) {
480 console.error("Failed to schedule self-update:", error);
481 + this.restarting = false;
482 + this.resetRestartState();
483 + this.removeProgressOverlay();
484 this.error = error.message || "Failed to schedule the self-update.";
485 } finally {
486 this.saving = false;
@@ -482,6 +497,7 @@ const model = {
497 );
498 this.ensureProgressOverlay();
499
500 + let restartRequestError = null;
501 try {
502 const token = await API.getCsrfToken();
503 const restartResponse = await fetch("/api/restart", {
@@ -495,9 +511,18 @@ const model = {
511 body: JSON.stringify({}),
512 });
513 if (restartResponse && !restartResponse.ok) {
498 - throw new Error(`Restart request failed with HTTP ${restartResponse.status}.`);
514 + restartRequestError = new Error(
515 + `Restart request failed with HTTP ${restartResponse.status}.`
516 + );
517 + throw restartRequestError;
518 + }
519 + } catch (error) {
520 + if (restartRequestError && error === restartRequestError) {
521 + this.restarting = false;
522 + this.resetRestartState();
523 + this.removeProgressOverlay();
524 + throw error;
525 }
500 - } catch (_error) {
526 // The restart request often terminates the backend mid-flight.
527 }
528
@@ -557,6 +582,7 @@ const model = {
582 }
583
584 this.restarting = false;
585 + this.resetRestartState();
586 this.removeProgressOverlay();
587 this.error =
588 "Agent Zero did not come back within the expected window. It may still be rolling back. " +