fix(gateway): a detached root gateway no longer boots the sticky active profile

The unsupervised child _spawn_detached_gateway starts carried no profile
selector for the root home, so main._apply_profile_override followed
active_profile: `restart --all --detach` (and the named-profile --all path,
the macOS launchctl fallback and the migration spawn, which share the
command) stopped the host and booted that profile's gateway instead, or
exited on its named-profile guard. _gateway_run_command pins
`--profile default` when the home is the root.

test_named_profile_restart_all_with_host_down_restarts_the_default_root
asserted `--profile` was absent to mean "not the named profile"; it now
asserts the selector is `default` and the named profile is not in argv.
This commit is contained in:
Teknium committed 2026-10-21 00:16:22 -07:00
1 parent c1c8543a13
commit 9ec17f7f4f
3 files changed
+19 -6

No files matched your search

+7 -1
View File
@@ -212,7 +212,13 @@ def _launchd_unsupported_marker_exists() -> bool:
def _gateway_run_command() -> list[str]:
from hermes_cli._launchers import runtime_command
return runtime_command(_gw().PROJECT_ROOT, [*shlex.split(_gw()._profile_arg()), "gateway", "run", "--replace"],
from hermes_constants import get_default_hermes_root
# Every caller spawns an UNSUPERVISED child, which follows the sticky active_profile unless a
# selector pins it (main._apply_profile_override): the root's replacement booted that profile.
selector = shlex.split(_gw()._profile_arg())
if not selector and _gw().get_hermes_home().resolve() == get_default_hermes_root().resolve():
selector = ["--profile", "default"]
return runtime_command(_gw().PROJECT_ROOT, [*selector, "gateway", "run", "--replace"],
python=_gw().get_python_path())
+1 -1
View File
@@ -211,7 +211,7 @@ def test_named_profile_restart_all_with_host_down_restarts_the_default_root(monk
assert len(spawned) == 1
cmd, kwargs = spawned[0]
assert cmd[-3:] == ["gateway", "run", "--replace"]
assert "--profile" not in cmd
assert cmd[-5:-3] == ["--profile", "default"] and "leinad" not in cmd
env = kwargs["env"]
assert Path(env["HERMES_HOME"]) == root
assert env.get("DISCORD_BOT_TOKEN") == "root-discord"
@@ -110,10 +110,15 @@ def test_detached_restart_keeps_force_for_the_replacement(monkeypatch):
@pytest.mark.platforms("linux")
def test_detached_restart_all_never_runs_the_host_gateway_in_the_caller(monkeypatch, tmp_path):
"""`restart --all --detach` from the root profile with no service took the in-process path."""
"""`restart --all --detach` from the root profile with no service took the in-process path.
The detached replacement is pinned to the root: a bare child follows the sticky active_profile."""
import sys
import hermes_constants
from hermes_cli import gateway as gw
(tmp_path / "profiles" / "work").mkdir(parents=True)
(tmp_path / "active_profile").write_text("work\n", encoding="utf-8")
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
hermes_constants._default_hermes_root_memo = None
monkeypatch.setattr(gw, "_refuse_from_inside_gateway", lambda *a, **k: None)
@@ -125,10 +130,12 @@ def test_detached_restart_all_never_runs_the_host_gateway_in_the_caller(monkeypa
monkeypatch.setattr(gw, "_wait_for_api_server_port_free", lambda **k: None)
monkeypatch.setattr(gw, "_discard_dead_host_record", lambda: False)
monkeypatch.setattr(gw, "_installed_service_kind_for", lambda windows: None)
monkeypatch.setattr(gw, "get_python_path", lambda: sys.executable)
monkeypatch.setattr(gw, "run_gateway", lambda **k: pytest.fail("the caller became the host gateway"))
detached = []
monkeypatch.setattr(gw, "_spawn_detached_gateway", lambda: detached.append(True) or True)
launched = []
monkeypatch.setattr(gw.subprocess, "Popen", lambda cmd, **k: launched.append(cmd) or SimpleNamespace())
gw._cmd_restart(SimpleNamespace(system=False, all=True, force=False, detach=True))
assert detached == [True]
assert len(launched) == 1
assert launched[0][-5:] == ["--profile", "default", "gateway", "run", "--replace"]