Skip to content

fix(windows): suppress console-window flashes from gateway subprocesses + cache nearest_root - #47971

Closed
hellofrommorgan wants to merge 1 commit into
NousResearch:mainfrom
hellofrommorgan:windows-no-window-nice-to-haves
Closed

fix(windows): suppress console-window flashes from gateway subprocesses + cache nearest_root#47971
hellofrommorgan wants to merge 1 commit into
NousResearch:mainfrom
hellofrommorgan:windows-no-window-nice-to-haves

Conversation

@hellofrommorgan

Copy link
Copy Markdown
Contributor

What & why

On Windows, the headless gateway runs as pythonw.exe (no console). When it shells out to console programs without the Win32 CREATE_NO_WINDOW flag, a cmd.exe window flashes on screen. The worst offenders fire on every agent file edit / turn, so the user sees windows popping up repeatedly during normal operation.

This was originally tracked down from pyright-langserver.CMD being launched via cmd.exe /c by the LSP client (npm shims are .cmd files), but the same gap exists in several other gateway-reachable spawn sites.

Changes

Console-flash suppression (CREATE_NO_WINDOW):

File Spawn Trigger
agent/lsp/client.py .cmd-wrapped language servers (pyright etc.) LSP diagnostics
agent/lsp/install.py npm / pip / go LSP auto-install first use of a server
tools/checkpoint_manager.py shadow-git snapshot every file edit
agent/coding_context.py per-turn git status/branch every turn
agent/context_references.py git / rg for @-file refs @ references

All pass creationflags gated on sys.platform == 'win32' so the value is an inert 0 on POSIX (still a valid Popen kwarg — no behavior change off Windows). capture_output is preserved (we hide the window, not detach the process), reusing the existing windows_hide_flags() helper where imported.

Performance:

  • agent/lsp/workspace.py: memoize nearest_root. It previously did an uncached upward filesystem walk (markers × parent dirs of .exists() stat calls) and is invoked ~5× per file write (via enabled_for, _get_or_spawn, _mark_broken_for_file, and the typescript double-resolve). Adds _root_cache mirroring the existing _workspace_cache, cleared in clear_cache() — same staleness profile as the git-worktree cache (reset on shutdown / hermes lsp restart).

Testing

  • All 6 modules import cleanly.
  • tests/agent/lsp/ suites pass (workspace, service, install, broken-set, backend-gate): 42 passed. The one pre-existing failure test_normalize_path_expands_tilde is an unrelated Windows HOME/USERPROFILE quirk present on main and untouched here.
  • Added a manual functional check confirming nearest_root cache hit/miss consistency, population, and clear_cache() reset.

Notes

  • No behavior change on Linux/macOS (the flag resolves to 0).
  • The LSP client architecture already pools language-server clients by (server_id, root) and reuses them — there is no per-write process churn; the window-flash was purely a spawn-flag omission.

…es + cache nearest_root

On Windows, the headless gateway (pythonw.exe) shells out to several console programs without CREATE_NO_WINDOW, so a cmd.exe window flashes on screen. Worst offenders fire on every agent file edit/turn:

- agent/lsp/client.py: .cmd-wrapped language servers (pyright-langserver.CMD) launched via cmd.exe /c
- agent/lsp/install.py: npm/pip/go LSP auto-install
- tools/checkpoint_manager.py: shadow-git snapshot on every edit
- agent/coding_context.py: per-turn git status/branch probe
- agent/context_references.py: git/rg for @-file references

All pass CREATE_NO_WINDOW (0x08000000) via creationflags, gated on sys.platform == 'win32' so it is an inert 0 on POSIX (still a valid Popen kwarg). capture_output is preserved (hide, not detach).

Also memoize agent/lsp/workspace.py::nearest_root, which did an uncached upward filesystem walk (markers x parents stat calls) ~5x per file write. Adds _root_cache mirroring the existing _workspace_cache, cleared in clear_cache(). Same staleness profile as the git-worktree cache.
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists labels Jun 17, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for identifying the LSP-specific Windows subprocess gap. The core issue remains on current main, but this branch needs a focused salvage rather than a clean cherry-pick.

Problems

  • agent/lsp/client.py:273-282 now contains start_new_session=True, added by 18a9467fc for a TUI process-group safety fix. Preserve it when adding the no-window flag.
  • agent/lsp/install.py:351-356 now delegates pip installation to hermes_cli/tools_config.py::_pip_install; its subprocess calls at hermes_cli/tools_config.py:640-677 do not yet pass no-window flags. The original direct pip hunk no longer reaches the runtime path.
  • No regression tests cover the LSP client/install flags or nearest_root cache invalidation. Existing Windows checks cover the already-landed coding-context, context-reference, and checkpoint paths (tests/test_windows_subprocess_no_window_flags.py:97-153, :341-356).

Suggested changes

  • Re-scope to the remaining LSP client/install paths and retain start_new_session=True.
  • Add focused Windows-flag and nearest_root cache/reset tests.
  • Omit the three paths already covered by cb1bb1a48 and cb982ad997.

Automated hermes-sweeper review.

Comment thread agent/lsp/workspace.py
@@ -140,6 +149,28 @@ def nearest_root(
markers_list = list(markers)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a regression test that proves a repeated identical lookup avoids a second filesystem walk and that clear_cache() restores lookup behavior. Current tests/agent/lsp/test_workspace.py covers root resolution but not this new cache contract.

@teknium1 teknium1 added sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
teknium1 pushed a commit that referenced this pull request Jul 23, 2026
…r subprocesses

Salvaged from PR #47971 (LSP subset). On Windows, .cmd-wrapped language
servers (e.g. pyright-langserver.CMD launched via cmd.exe /c) and the
npm/go/pip LSP auto-installers spawn without CREATE_NO_WINDOW, so a
console window flashes whenever the spawn happens under a console-less
parent — e.g. a VS Code/Zed extension host running the ACP adapter.

- agent/lsp/client.py::_spawn: pass creationflags=windows_hide_flags()
  to the language-server asyncio subprocess (inert 0 on POSIX;
  start_new_session is kept — it is POSIX-only and ignored on Windows).
- agent/lsp/install.py: same flags on the npm and go installer
  subprocess.run calls. The pip path goes through
  hermes_cli.tools_config._pip_install, which already hides its windows.

Adapted from the PR's hand-rolled _NO_WINDOW constant to the repo's
hermes_cli._subprocess_compat.windows_hide_flags() convention.
teknium1 added a commit that referenced this pull request Jul 23, 2026
…o installers

Regression tests for the #47971 salvage: the LSP language-server spawn
must pass windows_hide_flags() creationflags while keeping PIPE stdio
and start_new_session, and the npm/go LSP auto-installer subprocess.run
calls must carry the same hide flags with DEVNULL stdin and
capture_output intact.
teknium1 pushed a commit that referenced this pull request Jul 24, 2026
…r subprocesses

Salvaged from PR #47971 (LSP subset). On Windows, .cmd-wrapped language
servers (e.g. pyright-langserver.CMD launched via cmd.exe /c) and the
npm/go/pip LSP auto-installers spawn without CREATE_NO_WINDOW, so a
console window flashes whenever the spawn happens under a console-less
parent — e.g. a VS Code/Zed extension host running the ACP adapter.

- agent/lsp/client.py::_spawn: pass creationflags=windows_hide_flags()
  to the language-server asyncio subprocess (inert 0 on POSIX;
  start_new_session is kept — it is POSIX-only and ignored on Windows).
- agent/lsp/install.py: same flags on the npm and go installer
  subprocess.run calls. The pip path goes through
  hermes_cli.tools_config._pip_install, which already hides its windows.

Adapted from the PR's hand-rolled _NO_WINDOW constant to the repo's
hermes_cli._subprocess_compat.windows_hide_flags() convention.
teknium1 added a commit that referenced this pull request Jul 24, 2026
…o installers

Regression tests for the #47971 salvage: the LSP language-server spawn
must pass windows_hide_flags() creationflags while keeping PIPE stdio
and start_new_session, and the npm/go LSP auto-installer subprocess.run
calls must carry the same hide flags with DEVNULL stdin and
capture_output intact.
@teknium1

Copy link
Copy Markdown
Contributor

Merged via PR #70263 — your commit was cherry-picked onto current main with authorship preserved (rebase-merge). The LSP client + npm/go installer hunks landed; we converted the hand-rolled constant to the shared windows_hide_flags() helper and added regression tests on top. The other hunks were dropped as already-fixed on main (bounded_git_probe / existing flags), the pip path routes through a helper that already applies flags internally, and the nearest_root cache deserves its own review — feel free to re-cut that separately if you'd like it considered. Thanks for finding the one flash class that survives the daemon-level fix.

randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…r subprocesses

Salvaged from PR NousResearch#47971 (LSP subset). On Windows, .cmd-wrapped language
servers (e.g. pyright-langserver.CMD launched via cmd.exe /c) and the
npm/go/pip LSP auto-installers spawn without CREATE_NO_WINDOW, so a
console window flashes whenever the spawn happens under a console-less
parent — e.g. a VS Code/Zed extension host running the ACP adapter.

- agent/lsp/client.py::_spawn: pass creationflags=windows_hide_flags()
  to the language-server asyncio subprocess (inert 0 on POSIX;
  start_new_session is kept — it is POSIX-only and ignored on Windows).
- agent/lsp/install.py: same flags on the npm and go installer
  subprocess.run calls. The pip path goes through
  hermes_cli.tools_config._pip_install, which already hides its windows.

Adapted from the PR's hand-rolled _NO_WINDOW constant to the repo's
hermes_cli._subprocess_compat.windows_hide_flags() convention.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…o installers

Regression tests for the NousResearch#47971 salvage: the LSP language-server spawn
must pass windows_hide_flags() creationflags while keeping PIPE stdio
and start_new_session, and the npm/go LSP auto-installer subprocess.run
calls must carry the same hide flags with DEVNULL stdin and
capture_output intact.
@hellofrommorgan
hellofrommorgan deleted the windows-no-window-nice-to-haves branch August 18, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants