Run queued commands with stdin detached from the MCP transport - #38
Open
BitPhoenix wants to merge 1 commit into
Open
BitPhoenix wants to merge 1 commit into
BitPhoenix wants to merge 1 commit into
Conversation
A queued command inherited the server's stdin, which is the MCP stdio transport. Node (vitest, tsc, npm) marks any stdin it touches as non-blocking, and that flag lives on the pipe the server shares. After such a command exited, the server's next read failed, the server shut down cleanly, and the client saw "Connection closed" on whichever call was in flight, usually task_status. A command could also read the client's requests off the pipe. Commands now get /dev/null as stdin. The new test sets the flag from a Python child, so it needs no Node, and drives the server over a real stdio transport because the in-memory client has no stdin to share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
_execute_commandstarts each command with stdout and stderr piped, but doesn't set stdin. So the command inherits the server's stdin, which is the MCP stdio transport.Node (vitest, tsc, npm) marks any stdin it touches as non-blocking. That flag belongs to the pipe itself, so the server's copy changes too. Once such a command exits, the server's next read of stdin fails and the server shuts down cleanly (exit code 0). The client then reports
Connection closedon whichever call is open, usuallytask_statuswhile it waits for a long test or build run. A command could also read the client's requests off the pipe.We reproduced this without an MCP client in the loop. We drove the server over stdio and ran
node -e "process.stdin; setTimeout(() => {}, 45000)", and the server exited as the command did, 46 s in. The same command with</dev/nullleft it running. In our Claude Code sessions it dropped the connection on nearly every vitest or tsc run.Fix
Run commands with
stdin=asyncio.subprocess.DEVNULL.tqis unchanged: it deliberately passes the terminal through.Test
test_command_cannot_disturb_the_stdio_transportruns the server over a real stdio transport (PythonStdioTransportwith a temporary--data-dir). The in-memory client that the other tests use has no stdin to share. The test's command callsos.set_blocking(0, False), which has the same effect as Node without needing Node. It then callstask_status.McpError: Connection closed.ruff check .is clean.Note:
pyproject.tomlallows anyfastmcp>=2.14.4, and fastmcp 3+ no longer hasfastmcp.tools.tool. So a freshuv synccurrently resolves a fastmcp wheretests/test_queue.pyfails to import. I ran the tests withfastmcp>=2.14.4,<3. That's independent of this change.🤖 Generated with Claude Code