From 8e7c7af9fde28b04ffd95022e4d3264cdbb58562 Mon Sep 17 00:00:00 2001 From: Mikhail Koviazin Date: Thu, 27 Aug 2026 21:51:52 +0800 Subject: [PATCH 1/2] fix(environment): keep the package importable without unix sockets --- hud/environment/egress.py | 18 +++++---- hud/environment/namespace.py | 4 +- hud/environment/tests/test_platform.py | 52 ++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 hud/environment/tests/test_platform.py diff --git a/hud/environment/egress.py b/hud/environment/egress.py index 37ddc3732..836cdcb81 100644 --- a/hud/environment/egress.py +++ b/hud/environment/egress.py @@ -526,14 +526,16 @@ def handle(self) -> None: _relay(self.request, upstream) -class _UnixServer(socketserver.ThreadingUnixStreamServer): - daemon_threads = True - request_queue_size = socket.SOMAXCONN - - def get_request(self) -> tuple[socket.socket, tuple[str, int]]: - # A unix peer has no address; the handler wants one to log. - request, _ = super().get_request() - return request, ("workspace", 0) +if sys.platform != "win32": # the sockets this serves on have no Windows analogue + + class _UnixServer(socketserver.ThreadingUnixStreamServer): + daemon_threads = True + request_queue_size = socket.SOMAXCONN + + def get_request(self) -> tuple[socket.socket, tuple[str, int]]: + # A unix peer has no address; the handler wants one to log. + request, _ = super().get_request() + return request, ("workspace", 0) class Egress: diff --git a/hud/environment/namespace.py b/hud/environment/namespace.py index 569978abc..1245322f7 100644 --- a/hud/environment/namespace.py +++ b/hud/environment/namespace.py @@ -8,7 +8,6 @@ import json import logging import os -import pty import shutil import signal import socket @@ -22,6 +21,9 @@ from hud.environment.utils import splice from hud.utils.process import ProcessGroup, ProcessResult, create_process_group_exec +if sys.platform != "win32": # the pty a session runs on has no Windows analogue + import pty + _AF_NETLINK = getattr(socket, "AF_NETLINK", 16) _NETLINK_ROUTE = getattr(socket, "NETLINK_ROUTE", 0) LOGGER = logging.getLogger("hud.environment.namespace") diff --git a/hud/environment/tests/test_platform.py b/hud/environment/tests/test_platform.py new file mode 100644 index 000000000..86a22def8 --- /dev/null +++ b/hud/environment/tests/test_platform.py @@ -0,0 +1,52 @@ +"""Regression test for importing the package where unix sockets do not exist. + +Both routes out of a workspace are served on unix sockets and a session runs on a pty, +and each reached for its platform at module scope: a +``socketserver.ThreadingUnixStreamServer`` subclass in :mod:`hud.environment.egress`, +and a top-level ``import pty`` in :mod:`hud.environment.namespace`. Neither exists on +Windows, so ``import hud`` raised there before any of it was used, taking down the +commands that never go near a workspace with it. Both now sit behind the platform +guard the rest of the package already applies. + +The breakage is at import time, so the check runs in a fresh interpreter. That +interpreter imports the package once as the platform it really is, because the +standard library and the dependencies settle their own platform at their first import +and will not be told otherwise afterwards, and then imports it again with only the +package's own modules dropped, the platform reported as Windows, and the unix pieces +taken away. +""" + +from __future__ import annotations + +import subprocess +import sys + +_IMPORT_AS_WINDOWS = """ +import socketserver +import sys + +import hud + +for name in [n for n in sys.modules if n == "hud" or n.startswith("hud.")]: + del sys.modules[name] + +sys.platform = "win32" +sys.modules["pty"] = None # a Windows interpreter has no pty module to import +socketserver.__dict__.pop("ThreadingUnixStreamServer", None) # nor this server + +import hud + +print("IMPORTED") +""" + + +def test_the_package_imports_where_unix_sockets_do_not_exist() -> None: + result = subprocess.run( + [sys.executable, "-c", _IMPORT_AS_WINDOWS], + capture_output=True, + text=True, + timeout=120, + ) + + assert result.returncode == 0, f"stdout={result.stdout!r}\nstderr={result.stderr!r}" + assert "IMPORTED" in result.stdout From fffcbda9bd43b444165f76d15616ad7197d70558 Mon Sep 17 00:00:00 2001 From: Mikhail Koviazin Date: Thu, 27 Aug 2026 23:10:16 +0800 Subject: [PATCH 2/2] test(environment): import the unix server inside the test that uses it --- hud/environment/tests/test_workspace.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hud/environment/tests/test_workspace.py b/hud/environment/tests/test_workspace.py index 5b857ab59..1dd8c80e9 100644 --- a/hud/environment/tests/test_workspace.py +++ b/hud/environment/tests/test_workspace.py @@ -30,7 +30,7 @@ from hud.capabilities import SSHClient from hud.environment import namespace as namespace_mod from hud.environment import workspace as workspace_mod -from hud.environment.egress import Peer, _field, _UnixServer, _Unrelayable +from hud.environment.egress import Peer, _field, _Unrelayable from hud.environment.workspace import Bubblewrap, Mount, Workspace from hud.utils.process import ProcessGroup, ProcessResult @@ -657,6 +657,8 @@ async def spawn(argv: list[str], **kwargs: Any) -> Any: def test_peer_forwarders_use_the_substrate_listen_backlog() -> None: + from hud.environment.egress import _UnixServer # unix-only, as this module is + assert _UnixServer.request_queue_size == socket.SOMAXCONN