fix(stdio): serve bufferless std streams as text instead of crashing - #3090
fix(stdio): serve bufferless std streams as text instead of crashing#3090steps-re wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 2 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
The no-fd fallback reached for std.buffer, which raises AttributeError on bufferless text streams such as io.StringIO. Guard with hasattr and wrap the text stream directly in that case (nothing to tear down). Adds a regression test. Addresses the cubic review on modelcontextprotocol#3090. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@cubic-dev-ai good catch, and already addressed. The Covered by |
@steps-re Nice, this looks solid. The The test covers it well: monkeypatched Thanks for the clean follow-up — resolves the concern cleanly. Tip: get faster answers by chatting with cubic’s review copilot in the review UI |
_claim_fd falls back to `stream.buffer` whenever the stream is not backed by the expected descriptor. But _is_backed_by_fd also reports False when the stream has no `.buffer` at all, so that fallback dereferences an attribute it just proved might be missing. A sys.stdin/sys.stdout replaced with io.StringIO (test harnesses, and embedded hosts that swap the std streams) therefore raised AttributeError before serving a single message. Return None for the buffer in that case and serve the text stream in place: it is already text, so there is no binary layer to re-encode and none for _UnownedTextWrapper to protect from close. Signed-off-by: Mike German <mike@stepsventures.com>
b04f535 to
052edfe
Compare
|
rebased, and rescoped while doing it. #3117 landed the real fix for #1933 and did it better than my original commits, so i dropped them rather than carry a competing implementation. this is now a single commit on top of fails-before/passes-after repro is in the description. full suite 5582 passed, ruff and pyright clean, and stdio.py branch coverage is back at 100.00% for the fail_under gate. title and description updated to match the new scope. |
What this is now
This PR originally fixed #1933 (stdio_server closing the real process std handles). #3117 has since landed and fixes that properly, with a much better design than what I had here. So I have dropped my original commits entirely and rebased onto current
main.What is left is one narrower bug that #3117 does not cover.
The bug
_claim_fdstarts with:and
_is_backed_by_fdreturnsFalsein three cases, one of which is the stream having no.bufferat all:So on that path the fallback dereferences the attribute it just proved may be missing. If
sys.stdin/sys.stdouthave been replaced withio.StringIO,stdio_server()raises before serving a single message:Reproduced against
a4f4ccd0:It is not a hypothetical stream shape. Test harnesses do it, and so do embedded hosts that swap the std streams to capture output.
The fix
Return
Nonefor the buffer in that one case and serve the text stream in place. A bufferless stream is already text, so there is no binary layer to re-encode and none for_UnownedTextWrapperto protect from close. Every other path still returns a binary stream and is untouched.Verification
Local, on
a4f4ccd0with the commit applied:AttributeErrorbefore, exits cleanly aftertest_stdio_server_serves_bufferless_std_streams_in_placeround-trips a request and a response throughio.StringIOstreamstests/server/test_stdio.py18 passedruff checkandruff format --checkclean,pyright0 errorssrc/mcp/server/stdio.pyback to 100.00%, which thefail_under = 100gate needsDisclosure
Written with AI assistance, reviewed and verified by me. Happy to reshape or split this if you would rather have it another way.