-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
[Windows] subprocess.Popen with DEVNULL raises [WinError 6] when pytest captures stdin #14323
Description
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