You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bug description
When npx is launched on Windows, a related subprocess is initiated. When using the io.modelcontextprotocol.client.transport.StdioClientTransport#closeGracefully method to close, this.process.destroy() will only terminate the current process, but the subprocess cannot be closed, resulting in the main method not being able to end normally. Therefore, the taskkill pid /F /T command needs to be used to terminate it.
Perhaps something like the below snippet can be used to address this from the Java side:
Processprocess = Runtime.getRuntime().exec("npx ... ");
// When it's time to kill it:process.descendants().forEach(ProcessHandle::destroyForcibly);
process.destroyForcibly();
Potentially, this can be combined with:
process.onExit().thenAccept(p -> {
// Perform the additional cleanup here
});
Plan: use ProcessHandle.descendants() (JDK 9+, available since the repo's Java 17 baseline) instead of the OS-specific taskkill from the issue body — same effect, one cross-platform code path (Windows, macOS, Linux), no shelling out.
Implementation:
closeGracefully() walks process.descendants(), destroys each, then destroys the root.
A small package-private destroyProcessTree(Process) helper, visible for tests.
root with multiple long-running descendants (sh -c 'sleep 60 & sleep 60 & wait', POSIX-only via @DisabledOnOs(WINDOWS) since CI runs on Ubuntu)
root with no descendants
safe on an already-terminated process
./mvnw verify is green locally on macOS across all 11 modules, including StdioMcpSync/AsyncClientTests which exercise the close path through real npx.
Caveat: I'm on macOS, so the original Windows symptom is verified only by reasoning about the JDK API, not exercised end-to-end. Happy to switch to explicit taskkill if maintainers prefer that route.
Activity
chemicL commentedon Feb 20, 2026
Thanks.
Perhaps something like the below snippet can be used to address this from the Java side:
Potentially, this can be combined with:
k-krawczyk commentedon May 17, 2026
Hi! I'd like to take this.
Plan: use
ProcessHandle.descendants()(JDK 9+, available since the repo's Java 17 baseline) instead of the OS-specifictaskkillfrom the issue body — same effect, one cross-platform code path (Windows, macOS, Linux), no shelling out.Implementation:
closeGracefully()walksprocess.descendants(), destroys each, then destroys the root.destroyProcessTree(Process)helper, visible for tests.Tests added (
StdioClientTransportDestroyTreeTests):sh -c 'sleep 60 & sleep 60 & wait', POSIX-only via@DisabledOnOs(WINDOWS)since CI runs on Ubuntu)./mvnw verifyis green locally on macOS across all 11 modules, includingStdioMcpSync/AsyncClientTestswhich exercise the close path through realnpx.Caveat: I'm on macOS, so the original Windows symptom is verified only by reasoning about the JDK API, not exercised end-to-end. Happy to switch to explicit
taskkillif maintainers prefer that route.PR coming shortly.
Add a real test for the Windows tree-kill claim