From 4f7fe9bcffd95b4ab993c5b286f5b84e65d498fd Mon Sep 17 00:00:00 2001 From: Ben Barclay Date: Tue, 2 Jun 2026 10:36:10 +1000 Subject: [PATCH] fix(dashboard): surface Docker update guidance instead of generic failure (#34347) (#37085) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard Update button's backend guard (#36263) already returns a structured {ok:false, error:"docker_update_unsupported", message, update_command} envelope (HTTP 200) when running in a Docker install, instead of surfacing a raw SystemExit. But the frontend ignored that envelope: runAction() only branched on a thrown error, so the 200 fell through to the action-status poll, which reported a generic "Action failed (exit 1)" toast and never showed the actual guidance. Now runAction() inspects the update response and, on the docker_update_unsupported case, surfaces the backend's guidance message plus the recommended re-pull command directly (success-styled, since it's actionable guidance — not a crash) without starting the poll. Closes #34347. --- web/src/contexts/SystemActions.tsx | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/web/src/contexts/SystemActions.tsx b/web/src/contexts/SystemActions.tsx index de4ef4395..976bf4c32 100644 --- a/web/src/contexts/SystemActions.tsx +++ b/web/src/contexts/SystemActions.tsx @@ -71,10 +71,28 @@ export function SystemActionsProvider({ try { if (action === "restart") { await api.restartGateway(); + setActiveAction(action); } else { - await api.updateHermes(); + const resp = await api.updateHermes(); + // In a Docker install the image is immutable, so `hermes update` + // can't apply — the endpoint returns 200 with a structured + // {ok:false, error:"docker_update_unsupported", message, update_command} + // envelope instead of spawning the action (see #34347 / #36263). + // Surface that guidance to the user rather than starting the poll, + // which would otherwise report a generic "failed (exit 1)". + if (!resp.ok && resp.error === "docker_update_unsupported") { + const cmd = resp.update_command ? ` ${resp.update_command}` : ""; + setToast({ + type: "success", + message: + (resp.message ?? + "Updates don't apply inside Docker — re-pull the image instead.") + + cmd, + }); + return; + } + setActiveAction(action); } - setActiveAction(action); } catch (err) { const detail = err instanceof Error ? err.message : String(err); setToast({