stderr: 'pipe' deadlocks the session if the consumer never reads transport.stderr
Los mantenedores suelen responder en 1 día
@tiagovilasboas ya está trabajando en esto.
Desde el 11/9/2026.
- #2788 de @tiagovilasboas — cerrado sin fusionar
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 72/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- node.js, typescript
Línea de trabajo
Empieza por el manejo de stderr de StdioClientTransport y reproduce el bloqueo con stderr: 'pipe' cuando ningún consumidor lee transport.stderr. Compara el comportamiento de pipe con el de default o inherit y añade después una prueba de regresión que cubra un servidor que genera mucha salida sin un lector conectado; se considera terminado cuando la solicitud se completa sin bloquearse y un consumidor aún puede leer stderr.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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.
- Lenguaje dominante
- TypeScript
- Estrellas
- 13.5k
- Forks
- 2.3k
- Merge medio
- 1 d 22 h
- PR fusionados (30 d)
- 52
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de modelcontextprotocol/typescript-sdk
-
Stateless 405 response omits the Allow headerPosiblemente ocupada @jstar0 la tomó hace 2 días. Abiertov2
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
modelcontextprotocol/typescript-sdk#2970 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
[v2] @modelcontextprotocol/server inlines fast-uri 3.1.0, which has 9 published advisoriesPosiblemente ocupada @Andiii208 la tomó hace 2 días. Abiertov2
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
modelcontextprotocol/typescript-sdk#2966 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
v1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
modelcontextprotocol/typescript-sdk#2946 · 4 comentarios ·
Los mantenedores suelen responder en 1 día
-
[v2] URI template reserved expansions encode existing %HH sequences againPosiblemente ocupada @takagibit18 la tomó hace 9 días. Abiertov1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
modelcontextprotocol/typescript-sdk#2920 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
[v2] URI template strict expansions leave !'()* unencodedPosiblemente ocupada @takagibit18 la tomó hace 9 días. Abiertov1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
modelcontextprotocol/typescript-sdk#2919 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de modelcontextprotocol/typescript-sdk
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
JoviDeCroock/pracht#432 ·
Los mantenedores suelen responder en 1 día
-
Add: CNN en Espanol SDAbiertoapproved check:passed streams:add
Dificultad 1/5 Menos de una hora Aptitud para principiantes 75/100
Los mantenedores suelen responder en 1 día
-
Hardware attribute name "app Connection Support" has inconsistent casingPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
walletbeat/walletbeat#1628 ·
Los mantenedores suelen responder en 1 día
-
bug go
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
genkit-ai/genkit#6761 · 1 comentario ·
Los mantenedores suelen responder en 2 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
NousResearch/hermes-agent#136483 ·
Los mantenedores suelen responder en 1 día