fix(tui): prepend Hermes venv and user-local bin to slash_worker PATH (#83845)

This commit is contained in:
webtecnica
2026-08-11 08:16:24 -03:00
committed by Teknium
parent 47fd2eb7c8
commit 73b4e41ab2
2 changed files with 61 additions and 0 deletions
@@ -0,0 +1,42 @@
"""Regression test for slash_worker PATH construction (#83845).
When the gateway is launched by the Desktop/Dashboard app it can inherit a
minimal PATH (/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin)
that omits the Hermes venv bin dir and ~/.local/bin. The spawned
tui_gateway.slash_worker then cannot resolve Hermes-managed CLIs such as
browser-use/uvx via shutil.which, breaking browser_exec.
`tui_gateway.server._prepend_tool_paths` prepends those two directories to
the worker env PATH while preserving the inherited PATH.
"""
import os
import sys
from pathlib import Path
from tui_gateway import server as tui_server
class TestPrependToolPaths:
def test_prepends_venv_and_user_bin(self):
env = {"PATH": "/usr/bin"}
result = tui_server._prepend_tool_paths(env)
parts = result["PATH"].split(os.pathsep)
# venv bin first, then user-local bin, then the original PATH preserved
assert parts[0] == str(Path(sys.executable).parent)
assert str(Path.home() / ".local" / "bin") in parts
assert parts[-1] == "/usr/bin"
def test_preserves_existing_path_when_empty(self):
env = {}
result = tui_server._prepend_tool_paths(env)
parts = result["PATH"].split(os.pathsep)
assert parts[0] == str(Path(sys.executable).parent)
assert str(Path.home() / ".local" / "bin") in parts
def test_venv_bin_points_at_sys_executable_dir(self):
env = {"PATH": "/bin"}
result = tui_server._prepend_tool_paths(env)
assert result["PATH"].split(os.pathsep)[0] == str(Path(sys.executable).parent)
+19
View File
@@ -368,6 +368,20 @@ _stdio_transport = StdioTransport(lambda: _real_stdout, _stdout_lock)
_detached_ws_transport = _DropTransport()
def _prepend_tool_paths(env: dict[str, str]) -> dict[str, str]:
"""Prepend the Hermes venv bin dir and the user-local bin dir to PATH
so slash_worker child processes can resolve Hermes-managed CLIs
(browser-use, uvx) even when the parent gateway was launched with a
minimal PATH (e.g. by the Desktop/Dashboard app)."""
venv_bin = str(Path(sys.executable).parent) # <venv>/bin (POSIX) or <venv>/Scripts (Windows)
user_bin = str(Path.home() / ".local" / "bin")
existing = env.get("PATH") or ""
env["PATH"] = os.pathsep.join(
[p for p in (venv_bin, user_bin) if p] + ([existing] if existing else [])
)
return env
class _SlashWorker:
"""Persistent HermesCLI subprocess for slash commands."""
@@ -404,6 +418,11 @@ class _SlashWorker:
inherit_profile_home=False, # base already carries the HOME contract
extra={"HERMES_HOME": str(profile_home)} if profile_home else None,
)
# Prepend the Hermes venv bin dir and the user-local bin dir to PATH so
# slash_worker child processes can resolve Hermes-managed CLIs
# (browser-use, uvx) even when the parent gateway was launched with a
# minimal PATH (e.g. by the Desktop/Dashboard app). See #83845.
env = _prepend_tool_paths(env)
# start_new_session=True detaches the slash worker into its own
# process group / session. Without this, the worker inherits the