mcp: suppress subprocess exit error during graceful close#913
Open
blackwell-systems wants to merge 1 commit intomodelcontextprotocol:mainfrom
Open
mcp: suppress subprocess exit error during graceful close#913blackwell-systems wants to merge 1 commit intomodelcontextprotocol:mainfrom
blackwell-systems wants to merge 1 commit intomodelcontextprotocol:mainfrom
Conversation
When ClientSession.Close() initiates graceful shutdown via stdin close, the subprocess may exit with a non-zero status (e.g., gopls exits with code 2 because it treats EOF on stdin as fatal). This exit error propagated through pipeRWC.Close() -> closeErr -> ClientSession.Close(), causing spurious "exit status 2" errors for callers. The fix: in pipeRWC.Close(), after closing stdin and waiting for the subprocess to exit, suppress *exec.ExitError. The subprocess exited because we told it to (by closing stdin); the exit code is not meaningful for the client. Errors from SIGTERM/SIGKILL paths are still propagated, as those indicate the subprocess did not respond to graceful shutdown. This matches the analysis by @adonovan in golang/go#77336: "a bug in mcp causing a race between the active part of Close (which breaks the connection and terminates the child process) and the waiting part of close (which seems to observe an error from the terminated child without realizing that close itself was responsible)." Fixes modelcontextprotocol#855
|
cc: @findleyr |
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.
Summary
Fixes #855. When
ClientSession.Close()initiates graceful shutdown by closing stdin, the subprocess may exit with a non-zero status. This exit error propagated throughpipeRWC.Close()ascloseErr, causing spurious errors fromClientSession.Close().This matches the analysis by @adonovan in golang/go#77336:
Root cause
The error path traced by @adonovan:
ClientSession.Close()callsconn.Close(), which setsconnClosing = trueupdateInFlightsees idle + closing, callss.closer.Close()(pipeRWC.Close)pipeRWC.Close()closes stdin, then waits forcmd.Wait()cmd.Wait()returns*exec.ExitError, stored ascloseErrconn.wait(false)returnscloseErrto the callerThe subprocess exited because the client told it to (by closing stdin). The exit code is not meaningful for the client.
Fix
In
pipeRWC.Close(), after closing stdin and waiting for the subprocess to exit, suppress*exec.ExitError. Errors from the SIGTERM/SIGKILL fallback paths are still propagated, as those indicate the subprocess did not respond to graceful shutdown.The fix is at the transport layer (not the jsonrpc2 layer) because only the stdio transport has the semantic knowledge that "I closed stdin, so the subprocess exiting is expected."
Test plan
TestCmdTransportNonZeroExit: connects to a server that exits with code 2 on shutdown, verifiessession.Close()returns nilexit status 2) and passes with itGOWORK=off go test ./... -count=1