test(gateway): regression for multi-profile node symlink leak; AUTHOR_MAP

Add tmp_path symlink regression tests for both generate_systemd_unit and
generate_launchd_plist (~/.local/bin/node -> profile node install must not
leak the profile target into the generated unit PATH). Register
jearnest11's AUTHOR_MAP entry for the salvage cherry-pick.
This commit is contained in:
teknium1 2026-07-01 04:42:01 -07:00 committed by Teknium
parent 9138176dcd
commit 3b41df6d46
2 changed files with 43 additions and 0 deletions

View file

@ -234,6 +234,7 @@ AUTHOR_MAP = {
"157689911+itsflownium@users.noreply.github.com": "itsflownium",
"dirtyren@users.noreply.github.com": "dirtyren",
"153708448+hunjaiboy@users.noreply.github.com": "yyzquwu", # PR #47567 salvage (Matrix: register inbound handlers with wait_sync=True so _dispatch_sync's gather awaits them; without it mautrix fire-and-forgets and inbound intake has no completion point)
"jearnest@velocityenergy.com": "jearnest11", # PR #48700 salvage (multi-profile gateway flap: use node symlink's own parent, not .resolve() target, when building systemd/launchd service PATH so one profile's node path can't leak into every unit and force a perpetual daemon-reload restart loop)
"tgmerritt@gmail.com": "tgmerritt", # PR #43553 salvage (parse vLLM's token-based output-cap error format so over-cap max_tokens 400s reduce the output cap instead of death-looping into compression)
"13277570+justin-cyhuang@users.noreply.github.com": "justin-cyhuang",
"agent@tranquil-flow.dev": "Tranquil-Flow",

View file

@ -447,6 +447,48 @@ class TestGeneratedSystemdUnits:
assert "/home/test/.nvm/versions/node/v24.14.0/bin" in unit
def test_user_unit_does_not_leak_profile_node_symlink_target(self, tmp_path, monkeypatch):
# Regression for the multi-profile gateway restart-loop flap (#48700):
# ~/.local/bin/node is often a symlink into a *specific* profile's node
# install. The generated unit's PATH must contain the symlink's own
# directory (~/.local/bin), NOT the resolved profile target — otherwise
# one profile's node path leaks into every profile's unit, making
# systemd_unit_is_current() perpetually false and forcing a
# daemon-reload restart loop on every boot.
local_bin = tmp_path / ".local" / "bin"
profile_node_bin = tmp_path / ".hermes" / "profiles" / "jarvis" / "node" / "bin"
local_bin.mkdir(parents=True)
profile_node_bin.mkdir(parents=True)
real_node = profile_node_bin / "node"
real_node.write_text("#!/bin/sh\n")
link_node = local_bin / "node"
link_node.symlink_to(real_node)
monkeypatch.setattr(gateway_cli.shutil, "which", lambda cmd: str(link_node) if cmd == "node" else None)
unit = gateway_cli.generate_systemd_unit(system=False)
assert str(local_bin) in unit
assert str(profile_node_bin) not in unit
def test_launchd_plist_does_not_leak_profile_node_symlink_target(self, tmp_path, monkeypatch):
# Same #48700 regression for the macOS twin generate_launchd_plist().
local_bin = tmp_path / ".local" / "bin"
profile_node_bin = tmp_path / ".hermes" / "profiles" / "jarvis" / "node" / "bin"
local_bin.mkdir(parents=True)
profile_node_bin.mkdir(parents=True)
real_node = profile_node_bin / "node"
real_node.write_text("#!/bin/sh\n")
link_node = local_bin / "node"
link_node.symlink_to(real_node)
monkeypatch.setattr(gateway_cli.shutil, "which", lambda cmd: str(link_node) if cmd == "node" else None)
plist = gateway_cli.generate_launchd_plist()
assert str(local_bin) in plist
assert str(profile_node_bin) not in plist
def test_user_unit_includes_wsl_windows_interop_paths(self, monkeypatch):
monkeypatch.setattr(gateway_cli, "is_wsl", lambda: True)
monkeypatch.setenv(