fix(update): Windows keeps resuming after the fleet restart, never twice

The early before_build resume ran for Windows sets too. The fleet restart skips only what the
POSIX pause restarted (already_restarted), so a Windows gateway resumed before the build was
then drained and restarted a second time by the fleet pass: one extra outage per update that
main never had. The early resume is now POSIX-only and goes through the main facade like every
other resume call; Windows resumes where it always did. Docs say so.
This commit is contained in:
Teknium
2026-10-06 23:05:23 -07:00
parent 82d3d3b000
commit 082ec546aa
2 changed files with 11 additions and 9 deletions

View File

@@ -450,16 +450,18 @@ def _complete_selected(request: dict) -> bool:
def _resume_paused_before_build(request: dict) -> None:
"""The one restart point of gateways paused for this update, on every OS: the dependencies are
synced and the launchers published, the product builds (minutes) have not started. A gateway
needs nothing later to boot; config migration and maintenance run with it up, as they always
did on POSIX. A failure stays owed: the post-build resume retries it and records the outcome."""
"""Restart the gateways the POSIX pause stopped: the dependencies are synced and the launchers
published, the product builds (minutes) have not started. A gateway needs nothing later to
boot. A failure stays owed: the post-build resume retries it and records the outcome.
POSIX only: the fleet restart skips what this restarted (``already_restarted``). A Windows set
keeps resuming after the fleet restart, which would otherwise drain it a second time."""
token = request.get("windows_resume")
if not token or not token.get("resume_needed"):
if not token or not token.get("resume_needed") or token.get("platform") != "posix":
return
from hermes_cli.update_cmd_windows import _resume_windows_gateways_after_update
from hermes_cli.update_cmd import _m
try:
_resume_windows_gateways_after_update(token)
_m()._resume_windows_gateways_after_update(token)
except Exception as exc: # health: allow BLE001 -- retried (and recorded) after the build
print(f" ⚠ Paused gateway restart incomplete ({exc}); retrying after the build")

View File

@@ -98,7 +98,7 @@ For an admitted source checkout, `hermes update` runs these phases:
4. **Dependency preparation** — PM provisions required tools and prepares a complete Python environment from the new lock, existing extras, and enabled plugin requirements. It validates that environment before publishing its selection. A plugin never fails the update. A plugin that no longer fits the new core is added to `plugins.disabled` in every profile that enables it (a memory provider has `memory.provider` cleared). That covers a `requires-python` that excludes Hermes's Python, a `manifest_version` newer than this Hermes supports, and dependencies that the resolver proves can't resolve alongside core and earlier plugins in config order, or that fail their own build. A download or network failure gets one retry and is disabled if it fails again. The update prints `⚠ Disabled plugin '<name>' in <home>: <reason>`, records it in the receipt's warnings, and continues. Re-enable it with `hermes plugins enable <name>` once the plugin ships a compatible release, or once the network is back. A `requires_hermes` range the running version misses never disables a plugin, because a source checkout without release tags can read as an older release. That plugin sits out instead (`⚠ Left plugin '<name>' … out of this update`), stays enabled, and rejoins once Hermes reports a version it accepts. A secondary profile whose config cannot be read sits out the same way until the config is fixed. Only a core that cannot build on its own fails this step.
5. **Config migration** — detects new config options added since your version and prompts you to set them
6. **Desktop rebuild (stage-and-swap)** — if the Hermes Desktop app was built from this checkout, it is rebuilt so the GUI matches the new code. The rebuild packs into a temporary staging directory next to `apps/desktop/release/`, verifies the staged app, and only then renames it over the previous build (on Windows a real-time scanner briefly holding `release/win-unpacked` is ridden out with a few short retries). A rebuild that fails at any point — corrupt Electron download, missing dependency, disk full — leaves the previous app untouched and launchable; the update prints a `⚠` line, still finishes the remaining steps (config migration, gateway restart) and exits 0 with a `build` follow-up in its receipt, and the next launch, `hermes update` or `hermes desktop --build-only --force-build` retries the rebuild. On macOS the rebuilt bundle is then copied (with `ditto`, signature intact) over a stale `/Applications/Hermes.app` or `~/Applications/Hermes.app`, so the copy Finder and the Dock launch matches the backend; an installed copy that is currently running is left alone and the update tells you to quit it and run `hermes update` again. If a copy an earlier update installed has since been removed (including dragged to the Trash), the update rebuilds it if needed, puts it back and says so, so Finder, the Dock and Spotlight find Hermes again; `hermes uninstall` removes the app for good. On Windows, when the update finishes from inside the Desktop app it would rebuild (the app completing an interrupted update at launch), the rebuild is skipped with a notice instead: Windows locks a running app's files, and stopping the app would end the update with it. The rest of the update completes; quit Hermes Desktop and run `hermes desktop` from a terminal, or use **Update now** in **Settings → About**, to rebuild and reopen it.
7. **Gateway auto-restart**: gateways paused for the update (Linux, macOS and Windows) are started again as soon as the dependencies are prepared and the launchers refreshed, before the product builds; any other running gateway is refreshed after the update completes. Service-managed gateways (systemd on Linux, launchd on macOS) restart through the service manager. Manual gateways are relaunched when Hermes can map their PID to a profile. Manually launched `hermes serve` / `hermes dashboard` backends are different: the updater leaves them running and asks their owner to restart them. See [Manual backend restart reminders](#manual-backend-restart-reminders). Backends owned by a running Desktop app remain the app's responsibility.
7. **Gateway auto-restart**: on Linux and macOS, gateways paused for the update are started again as soon as the dependencies are prepared and the launchers refreshed, before the product builds; on Windows they restart after the builds. Any other running gateway is refreshed after the update completes. Service-managed gateways (systemd on Linux, launchd on macOS) restart through the service manager. Manual gateways are relaunched when Hermes can map their PID to a profile. Manually launched `hermes serve` / `hermes dashboard` backends are different: the updater leaves them running and asks their owner to restart them. See [Manual backend restart reminders](#manual-backend-restart-reminders). Backends owned by a running Desktop app remain the app's responsibility.
8. **Multiplex migration (multi-profile installs)** — once the fleet is verified on the new code, an install with two or more profiles that still run **one gateway per profile** is folded into a single multiplexed default gateway when nothing blocks it (same as `hermes gateway migrate --multiplex --yes`); if a blocker exists (a bot token shared by two profiles, a secondary profile binding a port with no `/p/<profile>/` ingress) the update prints the blockers with their fixes and changes nothing. Single-profile installs are never touched. See [Migrating from per-profile gateways](../user-guide/multi-profile-gateways.md#migrating-from-per-profile-gateways).
### Gateway downtime during an update
@@ -112,7 +112,7 @@ On Linux and macOS, `hermes update` stops every gateway running from this instal
- A gateway the updater cannot stop through its supervisor (an s6 or custom systemd unit, `--external-supervisor`, a launchd job that started this update) is left running and refreshed after the update, as before.
- `--no-gateway-restart` stops nothing; you manage the gateways.
Windows already paused its gateways before the update (they lock files the dependency sync replaces); they now restart at the same point.
Windows already paused its gateways before the update (they lock files the dependency sync replaces) and restarts them once the update completes, after the builds.
### Why the gateway restart can take a while