Skip to content

Commit dc30754

Browse files
HanSur94claude
andcommitted
fix(ci): avoid Windows pipe deadlock in integration test
Replace subprocess.PIPE with DEVNULL for stdout and a temp file for stderr. On Windows, piping both stdout and stderr causes a deadlock when FastMCP's banner fills the pipe buffer before Uvicorn starts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 26e947c commit dc30754

1 file changed

Lines changed: 17 additions & 6 deletions

File tree

tests/test_mcp_integration.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import signal
1414
import subprocess
1515
import sys
16+
import tempfile
1617
import time
1718
from typing import Any
1819

@@ -70,11 +71,16 @@ def mcp_server_process() -> Any:
7071
"streamablehttp",
7172
]
7273

74+
# Use temp file for stderr to avoid Windows pipe deadlock.
75+
# On Windows, subprocess.PIPE for both stdout and stderr can deadlock
76+
# when the FastMCP banner fills the pipe buffer before Uvicorn starts.
77+
stderr_file = tempfile.NamedTemporaryFile(mode="w+", suffix=".log", delete=False)
78+
7379
proc = subprocess.Popen(
7480
cmd,
7581
env=env,
76-
stdout=subprocess.PIPE,
77-
stderr=subprocess.PIPE,
82+
stdout=subprocess.DEVNULL,
83+
stderr=stderr_file,
7884
)
7985

8086
# Poll /mcp until server is ready or timeout expires.
@@ -101,10 +107,12 @@ def mcp_server_process() -> Any:
101107
pass
102108
# Check if process already died
103109
if proc.poll() is not None:
104-
stdout, stderr = proc.communicate()
110+
stderr_file.seek(0)
111+
stderr_text = stderr_file.read()
112+
stderr_file.close()
105113
raise RuntimeError(
106114
f"Server process exited early with code {proc.returncode}.\n"
107-
f"stderr: {stderr.decode(errors='replace')}"
115+
f"stderr: {stderr_text}"
108116
)
109117
time.sleep(_POLL_INTERVAL_S)
110118

@@ -114,12 +122,15 @@ def mcp_server_process() -> Any:
114122
proc.wait(timeout=_TEARDOWN_TIMEOUT_S)
115123
except subprocess.TimeoutExpired:
116124
proc.kill()
117-
_, stderr = proc.communicate()
125+
stderr_file.seek(0)
126+
stderr_text = stderr_file.read()
127+
stderr_file.close()
118128
raise RuntimeError(
119129
f"Server did not become ready within {_STARTUP_TIMEOUT_S}s.\n"
120-
f"stderr: {stderr.decode(errors='replace')}"
130+
f"stderr: {stderr_text}"
121131
)
122132

133+
stderr_file.close()
123134
yield proc
124135

125136
# Teardown: send SIGTERM, wait, then SIGKILL if needed

0 commit comments

Comments
 (0)