Skip to content

[Windows] subprocess.Popen with DEVNULL raises [WinError 6] when pytest captures stdin #14323

@XHXIAIEIN

Description

@XHXIAIEIN

Summary

subprocess.Popen(stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) raises [WinError 6] The handle is invalid when run inside pytest on Windows, but works fine when run directly with python.

The root cause: pytest's capture plugin replaces sys.stdin with a handle that Windows cannot DuplicateHandle() on. When subprocess.Popen does not receive an explicit stdin parameter, it inherits the parent's stdin and calls _make_inheritable()DuplicateHandle() on the captured handle, which fails.

Minimal reproduction

# test_repro.py
import subprocess
import sys

def test_popen_devnull_fails():
    """FAILS under pytest, PASSES when run directly."""
    p = subprocess.Popen(
        [sys.executable, "-c", "print('hello')"],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
        # stdin not specified - pytest's captured stdin triggers the bug
    )
    p.wait()
    assert p.returncode == 0

def test_popen_devnull_works_with_explicit_stdin():
    """PASSES in all cases - workaround."""
    p = subprocess.Popen(
        [sys.executable, "-c", "print('hello')"],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
        stdin=subprocess.PIPE,  # Explicitly providing stdin avoids the bug
    )
    p.wait()
    assert p.returncode == 0

if __name__ == "__main__":
    test_popen_devnull_fails()
    print("Direct run: PASS")

Results

Method test_popen_devnull_fails test_popen_devnull_works_with_explicit_stdin
python test_repro.py PASS PASS
pytest test_repro.py FAIL [WinError 6] PASS
pytest test_repro.py -s (no capture) PASS PASS

Traceback

test_repro.py::test_popen_devnull_fails FAILED

    p = subprocess.Popen(
        [sys.executable, "-c", "print('hello')"],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )
E   OSError: [WinError 6] The handle is invalid.

subprocess.py:1004: in __init__
    errread, errwrite) = self._get_handles(stdin, stdout, stderr)
subprocess.py:1379: in _get_handles
    p2cread = self._make_inheritable(p2cread)
subprocess.py:1430: in _make_inheritable
    h = _winapi.DuplicateHandle(

Analysis

When pytest's capture plugin is active, it replaces sys.stdin with a capture object. On Windows, subprocess.Popen._get_handles() calls msvcrt.get_osfhandle(sys.stdin.fileno()) to get the stdin handle for the child process. The captured stdin's file descriptor maps to a Windows handle that is not valid for DuplicateHandle().

Adding -s (disable capture) makes the test pass, confirming the issue is capture-related.

Workaround

Explicitly pass stdin=subprocess.PIPE to all subprocess.Popen calls.

Environment

  • Windows 11 Pro 10.0.26200
  • Python 3.14.3 (CPython, MSC v.1944 64 bit)
  • pytest 9.0.2
  • pluggy 1.6.0

Metadata

Metadata

Assignees

Labels

platform: windowswindows platform-specific problemplugin: capturerelated to the capture builtin plugin

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions