Skip to content

Frame stdio stdin by newline so plugins see one message at a time - #2026

Open
waldekmastykarz wants to merge 3 commits into
dotnet:mainfrom
waldekmastykarz:fix-stdio-placeholder-1757
Open

waldekmastykarz wants to merge 3 commits into
dotnet:mainfrom
waldekmastykarz:fix-stdio-placeholder-1757

Conversation

@waldekmastykarz

Copy link
Copy Markdown
Collaborator

Closes #1757

devproxy stdio treated every raw stdin read as one message. JSON-RPC messages written back-to-back (e.g. notifications/initialized immediately followed by tools/call) landed in a single read, so MockStdioResponsePlugin couldn't parse the body as JSON and left @stdin.body.id unresolved.

Changes

  • Add 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.ForwardStdinAsync now runs the plugin pipeline (logging, plugins, forward/mock, request log) once per framed message.
  • Stdout and stderr handling is unchanged.

Testing

  • Unit tests for StdioLineFramer (multiple lines per chunk, a line split across chunks, CRLF, a line over 4 KB, flush on EOF)
  • Integration test confirming @stdin.body.id resolves for a newline-terminated JSON-RPC message
  • The repro from the issue now returns "id":2

…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>
Copilot AI balanced review requested due to automatic review settings October 31, 2026 09:00
@waldekmastykarz
waldekmastykarz requested a review from a team as a code owner October 31, 2026 09:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

Comment thread DevProxy/Stdio/ProxySession.cs Resolved
…t#1757)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

Comment thread DevProxy/Stdio/ProxySession.cs Resolved
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues are identified for the stdin message-framing change.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MockStdioResponsePlugin: @stdin.body.id placeholder fails to resolve when messages arrive back-to-back after an id-less message

2 participants