fix(mcp): shut down gracefully on stdin EOF instead of orphaning to PID 1 (#751) - #837
Merged
Conversation
The SDK's StdioServerTransport subscribes to stdin 'data'/'error' only and never notices 'end'/'close'. When the parent MCP client dies, no teardown runs: whenever anything still references the event loop (a warm llama.cpp model's native handles being the common case), the server reparents to PID 1, leaks RAM, and keeps the SQLite index open. Treat stdin EOF as a client disconnect: close the server/transport, give in-flight request handlers a bounded window to settle (a new InflightGate counts running tool/resource handlers so their store/llm dependencies are not torn down underneath them), release the llama.cpp resources, close the store, and set process.exitCode instead of calling process.exit() so beforeExit still fires and node-llama-cpp's auto-dispose runs before libc's static destructors - forced exits during native-addon unload have caused exit-time crashes before (#59, and mirrors the HTTP transport's idempotent stop() including its stderr breadcrumb. The shutdown is a shared promise that never rejects: 'end', 'close', an already-ended stdin at registration time, and manual invocations all collapse into one run. Every step including logging is failure-tolerant (the parent's death may have closed stderr too), and a successful shutdown preserves an earlier nonzero exit code instead of masking it. Related: modelcontextprotocol/typescript-sdk#2003 fixes the same gap at the transport layer; this stays correct alongside it because the teardown is idempotent, and qmd needs its own store/llm cleanup either way. Tests: unit tests drive registerStdioEofShutdown via injected stdin (ordering incl. in-flight drain, idempotency, failure tolerance, exit-code preservation, throwing stderr, already-ended stdin) plus createInflightGate unit tests; an end-to-end test spawns the real server, completes an initialize round-trip, closes stdin, and asserts a prompt code-0 exit through the EOF shutdown path (stderr breadcrumb).
This was referenced Aug 12, 2026
|
Dear tobi/qmd,
We would like to acknowledge that we have received your request and a ticket has been created.
A support representative will be reviewing your request and will send you a personal response.(usually within 24-48 hours).
Thank you for your patience.
…On Wed, Aug 12 2026, at 11:59 PM, tobi/qmd ***@***.***> wrote:
Summary
Relands #767 onto current (#767) main (CHANGELOG conflict only). Fixes #751. (#751) qmd mcp (stdio) now shuts down gracefully when stdin reaches EOF instead of orphaning to PID 1 when the parent MCP client dies: close transport, bounded in-flight drain, close store (disposes llama.cpp), set process.exitCode (no forced process.exit()).
Test plan CI=true node ./node_modules/vitest/vitest.mjs run test/mcp-stdio-lifecycle.test.ts — 17/17 pass CI=true bun test --preload ./src/test-preload.ts test/mcp-stdio-lifecycle.test.ts — 17/17 pass
Supersedes #767. (#767) You can view, comment on, or merge this pull request online at: #837 (#837) Commit Summary b69669d fix(mcp): shut down gracefully when stdin reaches EOF (#751) (b69669d) File Changes
( 3 files) (https://github.com/tobi/qmd/pull/837/files) M CHANGELOG.md
(7) (https://github.com/tobi/qmd/pull/837/files#diff-06572a96a58dc510037d5efa622f9bec8519bc1beab13c9f251e97e657a9d4ed) M src/mcp/server.ts
(244) (https://github.com/tobi/qmd/pull/837/files#diff-9f457d54293270cc59a8245e10ad2f90e4b1bc82b1ec74ce6b1c415924069c73) A test/mcp-stdio-lifecycle.test.ts
(363) (https://github.com/tobi/qmd/pull/837/files#diff-1ddc3be2af909fe80af606cedf16a9a754c3f8e94feff2d7ac69e6dc1c563d1f) Patch Links: https://github.com/tobi/qmd/pull/837.patch (https://github.com/tobi/qmd/pull/837.patch) https://github.com/tobi/qmd/pull/837.diff (https://github.com/tobi/qmd/pull/837.diff)
—
Reply to this email directly, view it on GitHub, or (#837?email_source=notifications&email_token=BRF3HGYX2K5LC6M4HZS3GMD5JUAFRA5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF42DENRWHEYTENBYGSTHEZLBONXW5KTTOVRHGY3SNFRGKZFFMV3GK3TUVRTG633UMVZF6Y3MNFRWW) unsubscribe. (https://github.com/notifications/unsubscribe-auth/BRF3HG24BKO3YRIJA2RYNRD5JUAFRAVCNFSNUABGKJSXA33TNF2G64TZHMYTCMJSGM3DKMZQGE5US43TOVSTWNJRGM3DGNZTGU2TRILWAI)
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and (https://github.com/notifications/mobile/ios/BRF3HG5HQYXTF2C3DWKTGYL5JUAFRA5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF42DENRWHEYTENBYGSTHEZLBONXW5KTTOVRHGY3SNFRGKZFFMV3GK3TUVJTG633UMVZF62LPOM) Android. Download it today! (https://github.com/notifications/mobile/android/BRF3HG2OAT7EIM5WMQMVFML5JUAFRA5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF42DENRWHEYTENBYGSTHEZLBONXW5KTTOVRHGY3SNFRGKZFFMV3GK3TUVZTG633UMVZF6YLOMRZG62LE)
You are receiving this because you are subscribed to this thread. Message ID: <tobi/qmd/pull/837 @ github . com> [
{
***@***.***": "http://schema.org",
***@***.***": "EmailMessage",
"potentialAction": {
***@***.***": "ViewAction",
"target": "#837?email_source=notifications\u0026email_token=BRF3HG7QXHD6SCKCIHOWDND5JUAFRA5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF42DENRWHEYTENBYGSTHEZLBONXW5KTTOVRHGY3SNFRGKZFFMV3GK3TUVNTW2YLJNRPWG3DJMNVQ",
"url": "#837?email_source=notifications\u0026email_token=BRF3HG7QXHD6SCKCIHOWDND5JUAFRA5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF42DENRWHEYTENBYGSTHEZLBONXW5KTTOVRHGY3SNFRGKZFFMV3GK3TUVNTW2YLJNRPWG3DJMNVQ",
"name": "View Pull Request"
},
"description": "View this Pull Request on GitHub",
"publisher": {
***@***.***": "Organization",
"name": "GitHub",
"url": "https://github.com"
}
}
]
|
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
Relands #767 onto current
main(CHANGELOG conflict only). Fixes #751.qmd mcp(stdio) now shuts down gracefully when stdin reaches EOF instead of orphaning to PID 1 when the parent MCP client dies: close transport, bounded in-flight drain, close store (disposes llama.cpp), setprocess.exitCode(no forcedprocess.exit()).Test plan
CI=true node ./node_modules/vitest/vitest.mjs run test/mcp-stdio-lifecycle.test.ts— 17/17 passCI=true bun test --preload ./src/test-preload.ts test/mcp-stdio-lifecycle.test.ts— 17/17 passSupersedes #767.