The finally SIGCONT raised "No such process" when the gateway was already
gone, masking the exception that ended the try block. A stop-request wait
timeout now carries the pause record and the update log tail.
Review fixes for the pre-swap gateway pause (independent review of a029ec2):
- Every gateway is drained before the code swap: the control socket's
pause-for-update runs the request_restart drain (the post-update restart's
SIGUSR1 path) and the update waits within the fleet's canonical budget
(_gateway_drain_budget, 1905 s by default) with progress lines. systemd
units and launchd jobs are stopped through their supervisor only after
their drained gateway has exited, which also keeps Restart=/KeepAlive from
relaunching it mid-update. A gateway that does not take the drain gets the
supervisor stop / SIGTERM as before. The request is recorded (stop_sent)
before it is sent and carries no planned-stop marker: the marker watcher
takes the immediate signal-stop path and could fire first. Before: a
supervised gateway got systemctl stop / bootout at once
(restart_drain_timeout=0 cuts an in-flight turn), a bare one was SIGKILLed
after 70 s.
- The pause record stores canonical identities (pause_record.identity), so
recovery recognises a still-live gateway; the native birth stamp stays on
the entry for _alive.
- Supervised posix_units go through drop_never_stopped / split_draining /
backoff / hand-back like every other entry: a killed update no longer owes a
live unit it never stopped, nor relaunches one still draining.
- Unknown cgroup membership (either side unreadable) never authorises
stopping the unit; cgroup v1 name=systemd is read when v2 is absent.
- A record that cannot be written restores the token exactly (fresh or
adopted), so the checkout's tree gate is not armed by a pause that never
happened.
- A replayed bare gateway's environment follows the restart watcher it
replaces: the launch profile's own named home keeps the launcher env (an
exported bot token can be its only credential); every other home and the
multiplexing root get the served-profile factory.
- Resuming a paused unit runs the definition repair the fleet restart runs
first (_repair_unit_without_fatal_exit_park; launchd plist refresh).
- Shared POSIX tests use platforms("posix") instead of a module skipif, so
the macOS lane imports them.
e2e cell (e) now reads the recorded stop request (record stop_sent) instead of
the planned-stop marker, which the drain path no longer writes.
The bare-gateway replay spawned with {**os.environ, HERMES_HOME: home}. The updater's environ holds
the launch profile's .env, so replaying profile B's gateway from an updater run under profile A handed
B A's provider key and A's Telegram bot token (probe). Use served_profile_child_env(target_home=home,
inherit_credentials=True), the factory gateway_windows and gateway_launchd use for gateway spawns, and
drop _HERMES_GATEWAY like the launchd spawn does. Caught by test_subprocess_env_guard.
The macOS-only cell imported `_gw` from `hermes_cli.gateway`; that helper is a private late-import
shim inside `gateway_launchd`, not a facade export, so the cell died at import on the macOS lane
(skipped on Linux). `_launchd_print_service_pid` is the facade export it needed.
The exec row rebuilt an `import base64; exec(base64.b64decode(...))` string only to feed it back
through the base64 row; decoding the payload directly is shorter and leaves no line that reads like
the base64+exec payload signature the supply-chain scan (rightly) refuses. The test derives the two
re-entry forms from the existing launcher forms instead of re-spelling the cmd-launcher source.
When a launch through the published -c launcher (POSIX shell or Windows .cmd) syncs dependencies
first, venv_sync.relaunch_command re-execs it as `python -I -c "...; sys.argv = [...]; exec(<launcher
source literal>)"`. _BOOTSTRAPS covered the run_module/run_path re-entries but not this exec branch, so
the gateway it started was invisible to every canonical reader: live_gateway_pid_for_home, the fleet
restart, the stale-survivor sweep and update inventory. The new row matches the launcher in place by its
own anchors (readers normalize the literal escapes, so decoding it cannot work) and recurses into the
base64 row for the .cmd form. Extends the #124318 identity table and its test.
The post-commit cell assumed a manual `gateway run` that died at boot leaves the retry nothing to
relaunch. The pre-swap pause records its argv, so the retry relaunches it on HEAD; the cell now
asserts that, keeping the invariant that the obligation discharges only once a gateway serves HEAD
(observed: PID 6 -> 1210, code_sha == HEAD). The operator-restart leg is gone: it would start a twin.
The resume asked get_running_pid for the home's gateway.pid, which also needs that home's runtime
lock: a multiplexed host gateway serving the home was not found, so the recovery launch replayed
the paused argv, the replay exited 0 ("already serves"), and the restart was reported as failed
and stayed owed forever. live_gateway_pid_for_home is the reader every other caller uses (PID
file + lock, then the runtime status record).
A discovery or durable-record failure refused the whole update at arm_commit_point (exit 1),
so a host where systemctl cannot reach a user bus (SSH without lingering, containers) could no
longer update at all. The pause is protection, not a gate: any failure restarts whatever it
stopped and the update continues the way POSIX always updated (gateways run through the swap,
restarted after the commit). A failed rollback stays owed and restarts after the deps sync; a
successful one clears the restarted set so the fleet restart refreshes those gateways.
Caught by six existing completion/branch-switch tests whose fake subprocess layer makes
discovery raise; they pass unchanged.
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.
Explicit encoding + DEVNULL stdin on every subprocess.run, SIGKILL via getattr, BOM-tolerant
reads in the pause e2e cells (writes stay plain utf-8), and the macOS-only launchd test waives
os.getuid inline. The footgun lint runs on every file regardless of platform guards.
The supervisor check read an inherited INVOCATION_ID as "systemd will respawn this". Every
shell in a terminal hosted by systemd (Konsole, GNOME Terminal) carries one, and so does any
process deep under a CI runner's service, so the commonest bare gateway was left running on
old code. systemd restarts a unit only when its main process exits: a gateway now counts as
supervised only when it is a Restart= unit's MainPID or that MainPID's direct child.
macOS: launchd's application.<bundle id> names (iTerm2 shells) are no longer read as jobs.
The follow-up review found that the entry pointed at 6f464f72 while the engine
pin inside it still said a9641795, so an install fetched the old engine and lost
both of the fixes that round was meant to deliver. The pin is fixed in the
plugin itself now; this repins the catalog entry to that corrected commit so the
reviewed revision and the installed revision are the same thing again.
Also updated:
- `docs_url` and `image` now resolve at the new commit (both verified reachable).
- The description no longer implies "runs fully local" without qualification: it
notes that an embedding key already present in the environment is
auto-detected, and points at the README's egress section as the authoritative
list. That was the third finding.
Validation: `scripts/validate_plugin_catalog.py plugin-catalog/nexus.yaml` →
`OK: 1 file(s) valid`.
A second review pass over e420433 found five issues, one of them a real leak:
the endpoint matcher used a substring host test, so a lookalike domain could
receive a real provider key. Fixed in 616258b, along with the docstring that
promised a redirect the code never performed, a silent key rejection, an unused
import and a dependency-bound drift across three manifests.
Suite on the new pin: 2171 passed, 3 skipped.
Final revision: the mcp floor is corrected and the paid-station tests opt in
explicitly, so the audit passes on a clean machine. Audit run on this commit:
completed/success.
Carries the corrected mcp floor (>=2.0.0,<3.0.0). The previous pin resolved
mcp 1.x on CI, where `add_request_handler` does not exist, and the audit run
failed at collection. Reproduced and fixed; the CI job now passes in a clean
replica.
Points at 3be00e5, which carries the review fixes: opt-in cloud embedding,
opt-in query rewriting and paid stations, no cwd/.env load, key only paired
with its own endpoint, full egress documentation. The description no longer
claims a blanket 'runs fully local' without naming the opt-in paths.