-
-
Notifications
You must be signed in to change notification settings - Fork 627
Fix: PowerShell tool subprocess hang — replace capture_output with temp files + proc.wait() to break pipe inheritance deadlock #146
Description
Root Cause
execute_command() in service.py uses subprocess.run() with capture_output=True. This creates inheritable stdout/stderr pipe handles. When pip, npm, or other tools spawn grandchild processes, those grandchildren inherit the write end of the pipe. The direct child (PowerShell) exits, but grandchildren keep the pipe open — so communicate() blocks forever waiting for EOF. Result: 4+ minute MCP timeout on any subprocess-spawning command.
The existing stdin=subprocess.DEVNULL partial fix (v0.7.0) closes stdin but does NOT fix the stdout/stderr pipe deadlock.
Confirmed Affected Commands
pip --version,pip list,pip install-
npm --version
-
-
python -m pip --version
-
-
-
-
python -c "import subprocess; subprocess.run([...])"
-
-
-
-
-
-
- Any multi-line script with loops or multiple commands
-
-
-
Confirmed Working Commands (not affected)
echo,python --version,node --version,arduino-cli version
The Fix
Replace subprocess.run(capture_output=True) with subprocess.Popen() writing to temp files, then proc.wait(). This waits only for the direct child (PowerShell), not grandchildren. Temp files are never inherited across process boundaries.
import tempfile as _tempfile
_fd_out, _out_path = _tempfile.mkstemp(suffix="_out.txt", prefix="wmcp_")
_fd_err, _err_path = _tempfile.mkstemp(suffix="_err.txt", prefix="wmcp_")
try:
with os.fdopen(_fd_out, "wb") as _fout, os.fdopen(_fd_err, "wb") as _ferr:
proc = subprocess.Popen(
args,
stdin=subprocess.DEVNULL,
stdout=_fout,
stderr=_ferr,
cwd=os.path.expanduser(path="~"),
env=env,
creationflags=subprocess.CREATE_NO_WINDOW,
)
try:
returncode = proc.wait(timeout=timeout)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait()
return ("Command execution timed out", 1)
with open(_out_path, "rb") as f:
stdout = f.read().decode("utf-8", errors="replace")
with open(_err_path, "rb") as f:
stderr = f.read().decode("utf-8", errors="replace")
return (stdout or stderr, returncode)
finally:
for _p in (_out_path, _err_path):
try:
os.unlink(_p)
except Exception:
passVerified Fix
Applied this patch locally to service.py in the Claude Desktop extension. After restarting Claude Desktop:
pip --version→ instant response ✅-
pip list→ instant response ✅
-
-
npm --version→ instant response ✅
-
-
-
-
python -m pip --version→ instant response ✅
-
-
Environment
- Windows 11 AMD64
-
- Claude Desktop v1.1.8629.0 (MSIX install)
-
-
- windows-mcp v0.7.0 (Claude Desktop built-in extension)
-
-
-
-
- Python 3.13.12 (venv), Python 3.14.3 (uv system)
-
-