From 4276fe8ded72b63b4b849d253773ad02a96ba5dd Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 23 Jul 2026 12:24:28 -0700 Subject: [PATCH] test(windows): pin hide-flags contract for LSP client spawn and npm/go 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. --- ...test_windows_subprocess_no_window_flags.py | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/tests/test_windows_subprocess_no_window_flags.py b/tests/test_windows_subprocess_no_window_flags.py index a3b8e85a947d..3c99bdb50558 100644 --- a/tests/test_windows_subprocess_no_window_flags.py +++ b/tests/test_windows_subprocess_no_window_flags.py @@ -837,3 +837,107 @@ def test_codex_app_server_transport_hides_console_window(monkeypatch): # Hide-only: the app-server wire still needs its pipes. assert kwargs["stdin"] == subprocess.PIPE assert kwargs["stdout"] == subprocess.PIPE + + +# ── #47971 LSP spawn + installer paths (salvage) ──────────────────────────── +# +# The LSP language-server spawn (agent/lsp/client.py::_spawn) and the +# npm/go LSP auto-installers (agent/lsp/install.py) are reachable from +# console-less parents — a VS Code/Zed extension host running the ACP +# adapter — where a .cmd-wrapped server (pyright-langserver.CMD via +# cmd.exe /c) or an npm/go console app flashes a window on Windows. +# All are hide-only (creationflags); PIPE stdio must stay intact and the +# POSIX start_new_session detach must be preserved on the client spawn. + + +def test_lsp_client_spawn_hides_console_window(monkeypatch): + import asyncio + + from agent.lsp import client as lsp_client + + captured = [] + + class _FakeProc: + stdin = None + stdout = None + stderr = None + + async def fake_exec(*cmd, **kwargs): + captured.append((list(cmd), kwargs)) + return _FakeProc() + + monkeypatch.setattr(lsp_client, "windows_hide_flags", lambda: _CREATE_NO_WINDOW) + monkeypatch.setattr( + lsp_client.asyncio, "create_subprocess_exec", fake_exec + ) + + client = lsp_client.LSPClient( + server_id="test-server", + workspace_root="/tmp/ws", + command=["fake-langserver", "--stdio"], + ) + asyncio.run(client._spawn()) + + assert len(captured) == 1, captured + cmd, kwargs = captured[0] + assert cmd == ["fake-langserver", "--stdio"] + assert kwargs["creationflags"] == _CREATE_NO_WINDOW + # Hide-only: the LSP wire still needs its pipes, and the POSIX + # process-group detach (mcp orphan-sweep guard) must survive. + assert kwargs["stdin"] == asyncio.subprocess.PIPE + assert kwargs["stdout"] == asyncio.subprocess.PIPE + assert kwargs["start_new_session"] is True + + +def test_lsp_install_npm_hides_console_window(monkeypatch, tmp_path): + from agent.lsp import install as lsp_install + + captured = [] + + def fake_run(cmd, **kwargs): + captured.append((cmd, kwargs)) + return _Completed(stdout="") + + monkeypatch.setattr(lsp_install, "windows_hide_flags", lambda: _CREATE_NO_WINDOW) + monkeypatch.setattr(lsp_install.subprocess, "run", fake_run) + monkeypatch.setattr(lsp_install.shutil, "which", lambda name: f"/fake/bin/{name}") + monkeypatch.setattr( + lsp_install, "hermes_lsp_bin_dir", lambda: tmp_path / "lsp" / "bin" + ) + + # Bin lookup after the install misses (nothing staged) → None; the + # spawn contract is what is under test here. + lsp_install._install_npm("pyright", "pyright-langserver") + + spawns = _spawns(captured, "/fake/bin/npm", "install", "pyright") + assert len(spawns) == 1, captured + cmd, kwargs = spawns[0] + assert kwargs["creationflags"] == _CREATE_NO_WINDOW + assert kwargs["stdin"] == subprocess.DEVNULL + assert kwargs["capture_output"] is True + + +def test_lsp_install_go_hides_console_window(monkeypatch, tmp_path): + from agent.lsp import install as lsp_install + + captured = [] + + def fake_run(cmd, **kwargs): + captured.append((cmd, kwargs)) + return _Completed(stdout="") + + monkeypatch.setattr(lsp_install, "windows_hide_flags", lambda: _CREATE_NO_WINDOW) + monkeypatch.setattr(lsp_install.subprocess, "run", fake_run) + monkeypatch.setattr(lsp_install.shutil, "which", lambda name: f"/fake/bin/{name}") + monkeypatch.setattr( + lsp_install, "hermes_lsp_bin_dir", lambda: tmp_path / "lsp" / "bin" + ) + + lsp_install._install_go("golang.org/x/tools/gopls@latest", "gopls") + + spawns = _spawns(captured, "/fake/bin/go", "install") + assert len(spawns) == 1, captured + cmd, kwargs = spawns[0] + assert kwargs["creationflags"] == _CREATE_NO_WINDOW + assert kwargs["stdin"] == subprocess.DEVNULL + assert kwargs["capture_output"] is True