Repository navigation
Frame stdio stdin by newline so plugins see one message at a time - #2026
Open
waldekmastykarz wants to merge 3 commits into
Open
waldekmastykarz wants to merge 3 commits into
waldekmastykarz wants to merge 3 commits into
Conversation
…oses dotnet#1757 JSON-RPC messages written back-to-back were read as a single chunk, so MockStdioResponsePlugin couldn't parse the body as JSON and left @stdin.body.id unresolved. Stdin is now split on newlines (partial lines are buffered until the newline arrives or stdin closes) and each message runs through the plugin pipeline individually. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Mandatory newline framing can stall non-line-oriented executable sessions, and the added test does not exercise the stdin forwarding pipeline needed to catch the coalesced-input regression.
2 open issues
What changed in this PR
This PR addresses #1757 by framing stdin into newline-delimited messages before running Dev Proxy’s stdio plugin pipeline, allowing back-to-back JSON-RPC messages to be processed separately.
Changes:
- Adds byte-based framing that preserves line endings, buffers partial messages, and flushes remaining data on EOF.
- Processes logging, plugins, forwarding, and mocking once per stdin message; stdout and stderr remain unchanged.
- Adds framing unit tests and a plugin test for placeholder resolution with newline-terminated JSON.
| File | Description |
|---|---|
| DevProxy/Stdio/StdioLineFramer.cs | Adds newline framing and partial-message buffering. |
| DevProxy/Stdio/ProxySession.cs | Dispatches each framed stdin message through the plugin pipeline. |
| DevProxy.Tests/StdioLineFramerTests.cs | Tests chunk boundaries, CRLF, large messages, and flushing. |
| DevProxy.Integration.Tests/MockStdioResponsePluginTests.cs | Tests ID placeholder resolution in a newline-terminated request. |
Review effort: Balanced
…t#1757) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
A shared write semaphore can deadlock large framed messages with streaming children by blocking stdout drainage while stdin forwarding waits for the child to read more input.
1 open issue
2 resolved since last review
Review effort: Balanced
Writing a large framed stdin message while holding a shared semaphore could deadlock with streaming children because stdout couldn't drain. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.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.


Closes #1757
devproxy stdiotreated every raw stdin read as one message. JSON-RPC messages written back-to-back (e.g.notifications/initializedimmediately followed bytools/call) landed in a single read, soMockStdioResponsePlugincouldn't parse the body as JSON and left@stdin.body.idunresolved.Changes
StdioLineFramer, which splits stdin on newlines (JSON-RPC over stdio / MCP framing). Partial lines are buffered until the newline arrives; any remainder is flushed on EOF. Messages keep their trailing newline so forwarded bytes are unchanged.ProxySession.ForwardStdinAsyncnow runs the plugin pipeline (logging, plugins, forward/mock, request log) once per framed message.Testing
StdioLineFramer(multiple lines per chunk, a line split across chunks, CRLF, a line over 4 KB, flush on EOF)@stdin.body.idresolves for a newline-terminated JSON-RPC message"id":2