fix(gateway): avoid Windows uv pythonw launcher console - #41028
Conversation
|
Hi @ashu180 — your PR is still marked CONFLICTING 17 days after you opened it, and I had a near-duplicate open as #49615 (now closed as superseded). I rebased your commit onto current Conflict resolution summary: Two files conflicted during
Final diff stat after rebase: Verified on Note that #45610 ( Patch (apply with From 1128d65bb9eb24d5515e7f67635ec4e2ba18d191 Mon Sep 17 00:00:00 2001
From: ashu180 <18011166553@163.com>
Date: Sun, 7 Jun 2026 13:38:22 +0800
Subject: [PATCH] fix(gateway): avoid Windows uv pythonw launcher console
---
hermes_cli/gateway_windows.py | 25 ++++++++++++++++++++----
tests/hermes_cli/test_gateway_windows.py | 24 +++++++++++------------
2 files changed, 33 insertions(+), 16 deletions(-)
diff --git a/hermes_cli/gateway_windows.py b/hermes_cli/gateway_windows.py
index 994ab6e1c..72bc98e3e 100644
--- a/hermes_cli/gateway_windows.py
+++ b/hermes_cli/gateway_windows.py
@@ -344,15 +344,21 @@ def _build_gateway_cmd_script(
working_dir: str,
hermes_home: str,
profile_arg: str,
+ project_root: str | None = None,
) -> str:
"""Build the ``gateway.cmd`` wrapper content (CRLF-terminated).
The script:
- cd's into a stable working directory
- exports HERMES_HOME, PYTHONIOENCODING, VIRTUAL_ENV
- - invokes ``pythonw -m hermes_cli.main [--profile X] gateway run``
+ - invokes a no-console ``pythonw -m hermes_cli.main [--profile X] gateway run``
directly so the wrapper cmd.exe exits without a visible gateway console
+ uv-created venv launchers need special care: ``venv\\Scripts\\pythonw.exe``
+ can respawn the base interpreter as console ``python.exe``. Reuse the same
+ resolver as direct detached starts so Scheduled Task launches use the base
+ ``pythonw.exe`` plus PYTHONPATH entries for the repo and venv site-packages.
+
We intentionally do NOT inline PATH overrides here — cmd.exe inherits
the per-user PATH the Scheduled Task was created with, and forcibly
rewriting PATH tends to break Homebrew/nvm-style installations.
@@ -366,8 +372,13 @@ def _build_gateway_cmd_script(
# VIRTUAL_ENV lets the gateway's own python detection find the venv
# if someone imports hermes_constants-based logic during startup.
lines.append(f'set "VIRTUAL_ENV={venv_dir}"')
- pythonpath_entries = [str(Path(__file__).resolve().parent.parent), *extra_pythonpath]
- lines.append(f'set "PYTHONPATH={";".join([*pythonpath_entries, "%PYTHONPATH%"])}"')
+ repo_root = project_root or str(Path(python_path).resolve().parent.parent.parent)
+ pythonpath_entries = [repo_root, *extra_pythonpath]
+ if pythonpath_entries:
+ prefix = ";".join(str(Path(entry)) for entry in pythonpath_entries if entry)
+ lines.append(
+ f'if defined PYTHONPATH (set "PYTHONPATH={prefix};%PYTHONPATH%") else (set "PYTHONPATH={prefix}")'
+ )
prog_args = [pythonw_path, "-m", "hermes_cli.main"]
if profile_arg:
@@ -497,7 +508,13 @@ def _write_task_script() -> Path:
hermes_home = str(Path(get_hermes_home()).resolve())
profile_arg = _profile_arg(hermes_home)
- content = _build_gateway_cmd_script(python_path, working_dir, hermes_home, profile_arg)
+ content = _build_gateway_cmd_script(
+ python_path,
+ working_dir,
+ hermes_home,
+ profile_arg,
+ project_root=str(PROJECT_ROOT),
+ )
script_path = get_task_script_path()
tmp = script_path.with_suffix(".tmp")
tmp.write_text(content, encoding="utf-8", newline="")
diff --git a/tests/hermes_cli/test_gateway_windows.py b/tests/hermes_cli/test_gateway_windows.py
index c327039fc..b8d7c8688 100644
--- a/tests/hermes_cli/test_gateway_windows.py
+++ b/tests/hermes_cli/test_gateway_windows.py
@@ -210,17 +210,14 @@ def test_gateway_cmd_script_uses_pythonw_without_replace_or_start_churn(monkeypa
assert "exit /b 0" in content
-def test_gateway_cmd_script_uses_uv_safe_base_pythonw(monkeypatch, tmp_path):
- """Scheduled Task wrapper should share the detached uv-venv workaround."""
+def test_gateway_cmd_script_uses_base_pythonw_for_uv_venv_launcher(tmp_path):
+ """Scheduled Task wrapper must match direct detached starts for uv venvs."""
project = tmp_path / "project"
scripts = project / "venv" / "Scripts"
site_packages = project / "venv" / "Lib" / "site-packages"
- hermes_home = tmp_path / "hermes-home"
- base = tmp_path / "uv" / "python" / "cpython-3.11-windows-x86_64-none"
- scripts.mkdir(parents=True)
- site_packages.mkdir(parents=True)
- hermes_home.mkdir()
- base.mkdir(parents=True)
+ base = tmp_path / "uv-base"
+ for directory in (scripts, site_packages, base):
+ directory.mkdir(parents=True, exist_ok=True)
venv_python = scripts / "python.exe"
venv_pythonw = scripts / "pythonw.exe"
@@ -228,21 +225,24 @@ def test_gateway_cmd_script_uses_uv_safe_base_pythonw(monkeypatch, tmp_path):
for exe in (venv_python, venv_pythonw, base_pythonw):
exe.write_text("", encoding="utf-8")
(project / "venv" / "pyvenv.cfg").write_text(
- f"home = {base}\nimplementation = CPython\nuv = 0.11.14\nversion_info = 3.11.15\n",
+ f"home = {base}\nuv = true\n",
encoding="utf-8",
)
content = gateway_windows._build_gateway_cmd_script(
str(venv_python),
- str(hermes_home),
- str(hermes_home),
+ str(tmp_path / "hermes-home"),
+ str(tmp_path / "hermes-home"),
"",
+ project_root=str(project),
)
assert str(base_pythonw) in content
+ assert str(venv_pythonw) not in content
assert f'set "VIRTUAL_ENV={project / "venv"}"' in content
+ assert str(project) in content
assert str(site_packages) in content
- assert str(venv_pythonw) not in content
+ assert "PYTHONPATH=" in content
def test_elevated_gateway_command_uses_pythonw_hidden_console(monkeypatch):
--
2.45.1.windows.1
|
|
Thanks for the focused Windows gateway investigation. This is an automated hermes-sweeper review; current
The PR's behavioral goal is therefore already implemented on current main. |
Summary
Fix the Windows Gateway Scheduled Task wrapper so uv-managed installs do not leave a persistent foreground console/cmd window open.
The generated
Hermes_Gateway.cmdnow reuses the same detached interpreter resolver as direct Windows starts:pyvenv.cfgpythonw.exeinstead ofvenv\Scripts\pythonw.exewhen the venv launcher is a uv shimPYTHONPATHto include the source checkout and venvLib\site-packagesstart ""and--replaceWhy this matters
On Windows uv installs,
venv\Scripts\pythonw.execan behave like a uv shim that starts the base console-subsystempython.exe. When the Gateway is launched from the Scheduled Task through that shim, users can get a persistent blank console/cmd window.That is not just cosmetic: the window can be the Gateway's lifetime owner. Closing it can terminate the Gateway, so users are left with a bad choice:
Gateway service/start/restart paths should be background/no-window by default; only explicit
hermes gateway runshould be foreground/debug style.Related reports / prior art
This addresses the same root cause reported in:
This PR is intentionally focused on the Scheduled Task
.cmdgeneration path and adds a regression test that verifies uv venvs use the basepythonw.exeplusPYTHONPATH.Test plan
Ran on Windows:
Also verified the generated local task script uses base
pythonw.exe, includesVIRTUAL_ENV/PYTHONPATH, and contains nostart ""or--replacefor service-managed starts.