diff --git a/tools/environments/base.py b/tools/environments/base.py index 3f21f1294b..f0264ba3c9 100644 --- a/tools/environments/base.py +++ b/tools/environments/base.py @@ -489,6 +489,26 @@ class BaseEnvironment(ABC): def _drain(): fd = proc.stdout.fileno() + # select.select does NOT work on pipe fds on Windows (only sockets). + # Use blocking os.read in a daemon thread instead — safe because + # EOF arrives promptly when bash exits. + if os.name == "nt": + try: + while True: + chunk = os.read(fd, 4096) + if not chunk: + break + output_chunks.append(decoder.decode(chunk)) + except (ValueError, OSError): + pass + finally: + try: + tail = decoder.decode(b"", final=True) + if tail: + output_chunks.append(tail) + except Exception: + pass + return idle_after_exit = 0 try: while True: diff --git a/tools/environments/local.py b/tools/environments/local.py index 72d4f04d9c..f9094ee5b7 100644 --- a/tools/environments/local.py +++ b/tools/environments/local.py @@ -3,6 +3,7 @@ import logging import os import platform +import re import shutil import signal import subprocess @@ -403,6 +404,12 @@ class LocalEnvironment(BaseEnvironment): ) self.cwd = safe_cwd + # On Windows, self.cwd may be a Git Bash-style path (/c/Users/...) + # from pwd output. subprocess.Popen needs a native Windows path. + _popen_cwd = self.cwd + if _IS_WINDOWS and _popen_cwd and re.match(r'^/[a-zA-Z]/', _popen_cwd): + _popen_cwd = _popen_cwd[1].upper() + ':' + _popen_cwd[2:].replace('/', '\\') + proc = subprocess.Popen( args, text=True, @@ -413,7 +420,7 @@ class LocalEnvironment(BaseEnvironment): stderr=subprocess.STDOUT, stdin=subprocess.PIPE if stdin_data is not None else subprocess.DEVNULL, preexec_fn=None if _IS_WINDOWS else os.setsid, - cwd=self.cwd, + cwd=_popen_cwd, ) if not _IS_WINDOWS: try: