Skip to content

Fix: PowerShell tool subprocess hang — replace capture_output with temp files + proc.wait() to break pipe inheritance deadlock #146

@mkimerling

Description

@mkimerling

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:
            pass

Verified 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions