stderr: 'pipe' deadlocks the session if the consumer never reads transport.stderr
I maintainer di solito rispondono entro 1 giorno
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 72/100
- Tipo di issue
- Bug
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- node.js, typescript
Direzione di ricerca
Inizia dalla gestione di stderr di StdioClientTransport e riproduci il blocco con stderr: 'pipe' quando nessun consumer legge transport.stderr. Confronta il comportamento di pipe con quello di default o inherit, quindi aggiungi un test di regressione che copra un server loquace senza un reader collegato; il lavoro è completato quando la richiesta termina senza bloccarsi e un consumer può ancora leggere stderr.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
StdioClientTransport accepts stderr: 'pipe' and exposes the stream as transport.stderr, which is the documented way to read a server's diagnostics. If nobody attaches a reader, a server that logs a normal amount to stderr blocks on write, stops reading stdin, and the session stops — silently. No error, no rejection, no transport-level timeout. await client.listTools() simply never returns.
Reproduction
// s.mjs — a server that logs to stderr, which is what stderr is for
import { createInterface } from 'node:readline';
const send = o => process.stdout.write(JSON.stringify(o) + '\n');
createInterface({ input: process.stdin }).on('line', line => {
const m = JSON.parse(line);
if (m.method === 'initialize') return send({ jsonrpc:'2.0', id:m.id, result:{
protocolVersion:'2025-06-18', capabilities:{tools:{}}, serverInfo:{name:'chatty',version:'1.0.0'} }});
if (m.method === 'notifications/initialized') return;
for (let i = 0; i < 200; i++) process.stderr.write('log line '.repeat(5000) + '\n');
send({ jsonrpc:'2.0', id:m.id, result:{ tools:[{name:'x',description:'d',inputSchema:{type:'object'}}] }});
});
// c.mjs
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
const t = new StdioClientTransport({ command:'node', args:['s.mjs'], stderr:'pipe' });
const c = new Client({ name:'demo', version:'1.0.0' });
await c.connect(t);
if (process.argv[2] === 'drain') t.stderr.resume(); // the only difference
console.log('listTools()...');
const started = Date.now();
const r = await c.listTools();
console.log(`returned: ${r.tools.length} tools, ${Date.now()-started}ms`);
$ node c.mjs
listTools()...
← never returns
$ node c.mjs drain
listTools()...
returned: 1 tools, 19ms
@modelcontextprotocol/[email protected], Node 22.22.1. Same hang on stderr: 'overlapped'; inherit and the default are fine, since the OS drains them.
Why this bites in practice
The pipe fills, the child blocks in write(2), and a blocked child is not reading stdin either — so the request it was answering never gets answered. Standard pipe behaviour, but three things make it a bad failure here:
- The option is offered by the SDK and there is no warning attached to it.
stderr: 'pipe'reads as "let me see the server's logs", not "you are now responsible for draining a pipe or the session dies". - The failure is silent and looks like something else. No
onerror, no rejection. It presents as a slow or unresponsive server, so the natural first move is to blame the server or raise the request timeout — neither of which helps. - Nothing unusual has to happen. The server in the repro is writing to stderr, which is what stderr is for. Anything with verbose logging enabled crosses the threshold on a single response.
A consumer who attaches a reader late — after connect() resolves but after a first request has already gone out — hits it intermittently, which is worse than hitting it every time.
Suggestion
Any of these would close it; the first seems most in keeping with the rest of the transport:
- When
stderr: 'pipe'is requested and nothing is attached by the time the transport starts, drain it internally (.resume()) so the child never blocks. A consumer that attaches a listener still gets the data; one that does not gets a working session instead of a hang. - Alternatively, document the requirement on
StdioServerParameters.stderrand in thetransport.stderrgetter, in the form "you must consume this stream". - Or surface it: if the stream is unread and buffered beyond some size, emit through
onerrorrather than hanging.
Happy to open a PR for (1) with a regression test if that direction is right.
Related, though a different mechanism: #2678 fixes protocol errors being dropped when no onerror is set, and #2775 covers the real error reaching onerror while the awaiting caller gets Connection closed. This one is a third shape of the same underlying experience — the SDK is in a position to say what went wrong and the caller ends up with nothing.
- Lingua principale
- TypeScript
- Stelle
- 13.6k
- Fork
- 2.3k
- Merge medio
- 1g 15h
- PR unite (30g)
- 51
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di modelcontextprotocol/typescript-sdk
-
Stateless 405 response omits the Allow headerForse già presa @Hugo-DDT l’ha presa 1 giorno fa. Apertav2
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
modelcontextprotocol/typescript-sdk#2970 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
[v2] @modelcontextprotocol/server inlines fast-uri 3.1.0, which has 9 published advisoriesForse già presa @Andiii208 l’ha presa 3 giorni fa. Apertav2
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
modelcontextprotocol/typescript-sdk#2966 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
v1 v2
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
modelcontextprotocol/typescript-sdk#2946 · 4 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
[v2] URI template reserved expansions encode existing %HH sequences againForse già presa @takagibit18 l’ha presa 10 giorni fa. Apertav1 v2
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
modelcontextprotocol/typescript-sdk#2920 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
[v2] URI template strict expansions leave !'()* unencodedForse già presa @takagibit18 l’ha presa 10 giorni fa. Apertav1 v2
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
modelcontextprotocol/typescript-sdk#2919 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di modelcontextprotocol/typescript-sdk
Issue simili
-
bug help wanted
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
agent-task risk:low
Difficoltà 1/5 1-3 ore Idoneità per principianti 88/100
guyellis/http-status-check#512 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
microsoft/playwright#43327 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
microsoft/TypeScript#64758 ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
opensearch-project/OpenSearch-Dashboards#12937 ·
I maintainer di solito rispondono entro 2 giorni