fix(gateway): never report a copied profile dir as running (stale gateway.pid / gateway_state.json) - #119772
Closed
b2089766906-droid wants to merge 2 commits into
Closed
fix(gateway): never report a copied profile dir as running (stale gateway.pid / gateway_state.json)#119772b2089766906-droid wants to merge 2 commits into
b2089766906-droid wants to merge 2 commits into
Conversation
This was referenced Sep 23, 2026
teknium1
added a commit
that referenced
this pull request
Sep 23, 2026
…the multiplexer roster Slim redo of the copied-profile-dir guard (#119772) at the seams that actually leaked on main: - get_runtime_status_running_pid(expected_home=...) now rejects a record whose hermes_home stamp names another home (rung 3 of resolve_gateway_liveness and every direct caller: live_gateway_pid_for_home, the dashboard messaging/status readers, the update inventory). Rung 1 already applied recorded_gateway_home_conflicts inside get_running_pid. - A scoped read with no gateway_state.json hands rung 3 an empty record, never None -- None re-read the PROCESS home's record and lent its live PID to the copied directory. - multiplexer_liveness_for_profile only answers for <default root>/profiles/<name>: the roster is matched by name and a same-named directory under another root is not the served home. Two invariant tests replace the PR's four (same contract, real records instead of probe stubs).
teknium1
added a commit
that referenced
this pull request
Sep 23, 2026
Collaborator
|
Landed on main via #120085 (merge |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
A profile directory can be copied wholesale to another
HERMES_HOME(sandbox injection, restore-from-backup, cloning a profile). The copy carries that home'sgateway.pidandgateway_state.jsonalong, and the liveness ladder believed them: rung 1 trusts the PID inprofile_dir/gateway.pid, rung 3'sget_runtime_status_running_pid()re-reads the process home'sgateway_state.jsonwhen the scoped read finds nothing, and rung 4 matches the live multiplexer by profile name — which a copied directory keeps. Result:hermes -p X status/ the dashboard report a phantom gateway as running (usually with the production gateway's PID) whilehermes cron/gateway listsay the opposite on the same machine.The repo already has the right predicate for "this record belongs to another home" —
recorded_gateway_home_conflicts()(line 245) — but it only served the cross-profile kill guard (its only call site, line 1918). This PR wires it into the liveness ladder and adds the layout premise rung 4 was implicitly assuming.Symptom
A gateway appears "running" for a profile that has no gateway process, with a PID owned by a different
HERMES_HOME. The dashboard and the CLI contradict each other on the same page load;gateway stopon that profile then refuses or targets a foreign PID.Minimal reproduction (real output from this machine)
On
main(false green — the PID belongs to the production gateway of another home):With this PR:
Reverse check (must not be wounded — a genuinely served profile under the real default root), unchanged before and after:
Root cause (line level), all in
gateway/status.pyand the name-based matching both rung 3 (
_host_gateway_serves_home, line 643) and rung 4 (multiplexer_liveness_for_profile, line 1259) rely on:profile_name_for_home(profile_dir)returns"eagle"for a copied.../profiles/eaglejust as it does for the real one.Related Issue
No existing issue. Searched
resolve_gateway_liveness/ "gateway liveness profile copy": the neighbours are different shapes — #101487 (a multiplexed secondary profile showing as unreachable) and #117777 (a dead runtime PID must be marked stale) — neither covers "the record itself belongs to another home".Type of Change
Changes Made
gateway/status.py:_recorded_pid_home_conflict(profile_dir)— readsgateway.pidand defers to the existingrecorded_gateway_home_conflicts(..., expected_home=profile_dir)(legacy record withouthermes_homeproves nothing →False; comparison failure → fail closed →True);_profile_dir_is_default_root_profile(profile_dir)— is this really<default root>/profiles/<name>(the pooled layout rung 4 assumes)?gateway_state.json, and a scoped read that found no record is handed to the PID probe as an empty record instead ofNone, soget_runtime_status_running_pid()cannot silently fall back to the process home'sgateway_state.json;running=Falserather than matching a profile name across homes.tests/gateway/test_gateway_liveness_home_guard.py— new file, 4 tests (3 red onmain).Note for reviewers: the rung-3 "empty record" line came out of building the end-to-end repro above — without it the ladder still went green through the default home's record, so the fix would not have fixed the symptom. Happy to split it into its own PR if you prefer smaller commits.
How to Test
main=running=Truewith a foreign PID, this branch =running=False. Then check a genuinely served profile (<default root>/profiles/<name>under a livehermes --profile X serve) still readsrunning=True, source=multiplexer.main(patch reverted) — 3 of 4 fail, each on a different rung:mainand on this PR:The single failure is pre-existing and unrelated:
tests/gateway/test_status.py::TestReadProcessCmdlinePsFallback::test_ps_fallback_when_proc_unavailablefails on pristinemaintoo (Windows host, POSIX/proc-fallback test).What I could not verify
pytest tests/ -q(the whole suite) was not run — this host has no development clone with the full environment. Executed: the two new files plus the nine suites listed above, on Windows 11 / Python 3.11.16, against the shipped tree.pytest, so pytest ran from a throwawayuvenvironment with the tree's ownsite-packagesonPYTHONPATH.default|eagle|lite|monitor|render. Rungs 3/4 depend on live host-topology records, so the "no live multiplexer" and "multi-host" shapes are only covered by the injected seams in the unit tests.<default root>/profiles/would now reportrunning=Falseat rung 4 (the layout guard). That is the intent here, but if a supported deployment has that shape it needs an explicit premise instead of my assumption.hermes gateway stop/ the dashboard's own call sites passprofile_dirthe same way my probe does; the change is inside the shared ladder, so they inherit it.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass — not run (no full dev clone here); see "What I could not verify"Documentation & Housekeeping
docs/, docstrings) — docstrings on both new helperscli-config.yaml.exampleif I added/changed config keys — or N/A (no config keys)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/A_canonical_hermes_home(host case semantics); paths compared withresolve(strict=False)Screenshots / Logs
Before/after
resolve_gateway_liveness()output for a copied profile directory and for a genuinely served profile: see "Minimal reproduction" and "How to Test" above.