Skip to content

fix(gateway): eliminate console window on uv-venv Windows gateway launches - #49615

Closed
hqy2435662352 wants to merge 1 commit into
NousResearch:mainfrom
hqy2435662352:fix/uv-pythonw-console-window
Closed

fix(gateway): eliminate console window on uv-venv Windows gateway launches#49615
hqy2435662352 wants to merge 1 commit into
NousResearch:mainfrom
hqy2435662352:fix/uv-pythonw-console-window

Conversation

@hqy2435662352

@hqy2435662352 hqy2435662352 commented Jun 20, 2026

Copy link
Copy Markdown

Problem

On Windows hosts with uv-managed venvs, hermes gateway install (followed by Scheduled Task execution) and manual double-click on the generated .cmd wrapper both open a visible console window with the gateway process running inside it. This contradicts the documented design intent of _build_gateway_cmd_script, which explicitly uses pythonw.exe so that no gateway console window appears.

The root cause is uv's venv launcher. Standard venvs ship venv\Scripts\pythonw.exe as a true GUI-subsystem executable (no console). uv-managed venvs ship it as a launcher that reads pyvenv.cfg and respawns the base python.exe — a console-subsystem executable that opens a visible Windows Terminal tab.

_resolve_detached_python already handles this correctly for direct Popen spawns. _build_gateway_cmd_script (which generates the Scheduled Task .cmd wrapper) did not — it unconditionally used the venv launcher path.

Fix

Three changes in _build_gateway_cmd_script:

  1. Detect uv venvs and use base pythonw.exe: read pyvenv.cfg; when uv is present, resolve the base pythonw.exe from the home key. Same logic as _resolve_detached_python.

  2. Set PYTHONPATH: switching to the base interpreter (outside the venv) breaks -m hermes_cli.main because the .cmd wrapper cds into the profile home, not the repo checkout. PYTHONPATH=project_root;site-packages restores module resolution.

  3. start "" /B + stdout redirect: start /B lets the .cmd wrapper exit immediately instead of lingering as long as the gateway runs. Without an inherited hidden console, stdout writes from the detached gateway process would trigger Windows to allocate a visible one — so stdout/stderr are redirected to gateway-stdio.log (matching _spawn_detached's approach).

Generated .cmd wrapper (after fix)

@echo off
cd /d C:\...\profiles\thinktank
set "HERMES_HOME=..."
set "PYTHONIOENCODING=utf-8"
set "HERMES_GATEWAY_DETACHED=1"
set "VIRTUAL_ENV=..."
set "PYTHONPATH=project_root;site-packages;%PYTHONPATH%"
if not exist "...\logs" mkdir "...\logs"
start "" /B ...\pythonw.exe -m hermes_cli.main gateway run >> "...\gateway-stdio.log" 2>&1
exit /b 0

Risk Assessment

  • Non-uv venvs: uv detection gated on "uv" in cfg and home; fallback to original _derive_venv_pythonw path. Zero change.
  • Linux / macOS: only runs on sys.platform == "win32". Zero regression.
  • No new env vars, config changes, or dependencies.

Verification

Windows 10, uv-managed Python 3.11, three profiles (thinktank, default, invest):

  • Scheduled Task launch: no console window
  • Manual double-click on .cmd: no window
  • Gateway starts and connects to Lark successfully
  • hermes gateway status reports correct PIDs
  • 34/34 tests pass

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery duplicate This issue or pull request already exists labels Jun 20, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #41028 — same root cause (uv venv pythonw.exe launcher re-execs the console-subsystem python.exe), same file (hermes_cli/gateway_windows.py), and same fix mechanism (detect uv via pyvenv.cfg in _build_gateway_cmd_script, switch to the base pythonw.exe, and set PYTHONPATH for repo root + venv site-packages). #41028 is the earlier open PR for the same issue (#38387). This PR additionally adds start "" /B to close the lingering double-click console window, but that is a superset at the same fix site rather than a distinct mechanism.

@hqy2435662352

Copy link
Copy Markdown
Author

Understood! I'll keep this PR open. Once #41028 is merged into main, I will immediately rebase this branch onto main, strip out the duplicate UV detection bits, and pivot this PR into a dedicated follow-up that brings the start "" /B wrapper and stdout log redirection to the codebase.

Background
----------

On Windows hosts with uv-managed venvs, `hermes gateway install`
(Scheduled Task execution) and manual double-click on the generated
`.cmd` wrapper both open a visible console window containing the running
gateway process.  This contradicts the documented design intent of
`_build_gateway_cmd_script`, which already uses `pythonw.exe`.

Root cause: uv's venv launcher.  Standard venvs ship
`venv\Scripts\pythonw.exe` as a true GUI-subsystem executable.  uv
managed venvs ship it as a launcher that reads `pyvenv.cfg` and
respawns the base `python.exe` — a console-subsystem executable that
opens a visible Windows Terminal tab.

`_resolve_detached_python` already handles this correctly for direct
`Popen` spawns.  `_build_gateway_cmd_script` (the Scheduled Task
`.cmd` wrapper generator) did not — it unconditionally used the venv
launcher path.

Change
------

`hermes_cli/gateway_windows.py::_build_gateway_cmd_script`:

1. Detect uv venvs and switch to the base `pythonw.exe`.  Read
   `pyvenv.cfg`; when `uv` is present, resolve the base interpreter
   from the `home` key.  Mirrors `_resolve_detached_python`.

2. Set `PYTHONPATH=project_root;site-packages`.  Switching to the
   base interpreter (outside the venv) breaks `-m hermes_cli.main`
   because the `.cmd` wrapper `cd`s into the profile home, not the
   repo checkout.  PYTHONPATH restores module resolution.

3. Switch the wrapper invocation to `start "" /B` and redirect
   stdout/stderr to `gateway-stdio.log`.  `start /B` lets the wrapper
   exit immediately instead of lingering as long as the gateway
   runs.  Without an inherited hidden console, stdout writes from
   the detached process would trigger Windows to allocate a visible
   one — so the redirect matches `_spawn_detached`'s approach.

Generated `.cmd` wrapper (after fix):

    @echo off
    cd /d C:\...\profiles\thinktank
    set "HERMES_HOME=..."
    set "PYTHONIOENCODING=utf-8"
    set "HERMES_GATEWAY_DETACHED=1"
    set "VIRTUAL_ENV=..."
    set "PYTHONPATH=project_root;site-packages;%PYTHONPATH%"
    if not exist "...\logs" mkdir "...\logs"
    start "" /B ...\pythonw.exe -m hermes_cli.main gateway run >> "...\gateway-stdio.log" 2>&1
    exit /b 0

Tests
-----

`tests/hermes_cli/test_gateway_windows.py::test_gateway_cmd_script_uses_start_b_without_replace_churn`
updated to assert the new `start "" /B` line, the `>> log 2>&1`
redirect, and the `mkdir` log dir guard.  Existing assertions for
`pythonw.exe`, `gateway run`, no `--replace`, and `exit /b 0` retained.

Risk
----

- Non-uv venvs: uv detection gated on `"uv" in cfg and home`;
  fallback to original `_derive_venv_pythonw` path.  Zero change.
- Linux / macOS: only runs on `sys.platform == "win32"`.  Zero
  regression.
- No new env vars, config changes, or dependencies.

Out of scope
------------

- `tools/environments/local.py::_run_bash` and
  `agent/shell_hooks.py::_spawn` are separate call paths (terminal
  tool child processes, shell hook subprocesses) and are not
  affected by this fix.  Console suppression for those paths, if
  needed, should land in dedicated follow-ups targeting the
  specific user-visible symptom.
@hqy2435662352
hqy2435662352 force-pushed the fix/uv-pythonw-console-window branch from 279c715 to ba5b722 Compare June 21, 2026 06:44
@hqy2435662352

hqy2435662352 commented Jun 24, 2026

Copy link
Copy Markdown
Author

Closing this — and recommending #41028 be closed too. The reasoning is cleaner than what I wrote on 2026-06-24, so I am rewriting the comment in place rather than appending.

Two changes from main made this PR's premise obsolete; the third one I missed earlier:

  1. uv launcher handling (fix(windows): harden gateway scheduled task #45610, merged 2026-06-23). The _resolve_detached_python helper now picks the correct detached interpreter + PYTHONPATH for both standard venvs and uv-managed installs (hermes_cli/gateway_windows.py:537, called from the wrapper-rendering path at line 587). That fully absorbs the uv-detection half of this PR. fix(windows): harden gateway scheduled task #45610 also added _build_scheduled_task_xml so the Scheduled Task action itself launches pythonw.exe without a console — addressing the original "console window on auto-start" symptom without touching the .cmd wrapper text.

  2. _build_gateway_cmd_script already routes through pythonw.exe directly (hermes_cli/gateway_windows.py:366-377). pythonw.exe is a GUI-subsystem binary, so cmd.exe returns immediately after launching it. The wrapper's docstring at line 350 explicitly says "without a visible gateway console" and the body comment at lines 371-374 says:

    pythonw.exe is a GUI-subsystem executable: cmd.exe launches it and returns immediately, so the Scheduled Task action finishes without a visible console window. Do NOT use start here; that creates an extra wrapper process and made gateway lifecycle/status harder to reason about.

    tests/hermes_cli/test_gateway_windows.py:205 then asserts assert "start \"\"" not in content as a regression guard — i.e., this is an intentional design decision on main, not an oversight.

  3. The .cmd wrapper is a deliberately retained artifact. It is the Scheduled Task action path (get_task_script_path(), printed as Task script: <path> on install at lines 727/808/849) and is consulted by is_startup_entry_installed() (line 967). The maintainer kept it intentionally so _install_startup_entry (line 496) can fall back to a Startup-folder .cmd if schtasks registration fails. Wrapping it in start "" /B would add an extra wrapper process exactly for the case main's docstring warns against ("harder to reason about").

Net effect on #49615: both halves of this PR were either absorbed by #45610 (uv handling) or run counter to main's intentional design (start "" /B). Nothing independent remains to ship.

Recommendation for #41028: it has the same shape — uv launcher + start "" /B for the .cmd wrapper — and the same conflict with main's Do NOT use "start" policy. If a maintainer wants to land the additional test assertion it proposes, that can be folded into a separate, single-purpose PR. Until then, the PR should stay closed with a pointer here.

Salvage credit: if anything from #49615 ends up being reused, the original author is this PR's author — Co-authored-by: + a comment in any salvaging PR is appropriate per the project's contributor-credit rule.

Not opening a follow-up myself. If a future use case actually triggers a visible console from double-clicking the wrapper, that would be worth a fresh PR with the real reproduction, not a continuation of this one's reasoning.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants