stderr: 'pipe' deadlocks the session if the consumer never reads transport.stderr
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 初心者へのやさしさ
- 72/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
- 技術スタック
- node.js, typescript
調査の方向性
StdioClientTransport の stderr 処理から始め、どの consumer も transport.stderr を読み取らない状態で stderr: 'pipe' にするとハングすることを再現します。pipe と default または inherit の動作を比較し、その後、reader が接続されていない chatty server を対象とする回帰テストを追加します。完了条件は、リクエストがブロックせずに完了し、consumer が引き続き stderr を読み取れることです。
索引モデルが issue の本文から書いたものです。
説明
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.
- 主要言語
- TypeScript
- スター
- 13.5k
- フォーク
- 2.3k
- 平均マージ
- 1日 22時間
- マージ済み PR(30日)
- 52
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
modelcontextprotocol/typescript-sdk のほかの issue
-
Stateless 405 response omits the Allow header対応中かも @jstar0 が 1 日前に担当しました。 オープンv2
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
modelcontextprotocol/typescript-sdk#2970 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
[v2] @modelcontextprotocol/server inlines fast-uri 3.1.0, which has 9 published advisories対応中かも @Andiii208 が 1 日前に担当しました。 オープンv2
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
modelcontextprotocol/typescript-sdk#2966 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
v1 v2
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
modelcontextprotocol/typescript-sdk#2946 · コメント 4 件 ·
メンテナーはふだん 1 日以内に返信
-
[v2] URI template reserved expansions encode existing %HH sequences again対応中かも @takagibit18 が 7 日前に担当しました。 オープンv1 v2
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
modelcontextprotocol/typescript-sdk#2920 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
[v2] URI template strict expansions leave !'()* unencoded対応中かも @takagibit18 が 8 日前に担当しました。 オープンv1 v2
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
modelcontextprotocol/typescript-sdk#2919 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
modelcontextprotocol/typescript-sdk の issue をすべて見る
似ている issue
-
check:passed streams:add
難易度 1/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 2 日以内に返信
-
beta technical-medium ui
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
walletbeat/walletbeat#1625 ·
メンテナーはふだん 1 日以内に返信
-
[Good First Issue]: Add unit tests for NetworkVersionInfo対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンGood First Issue hacktoberfest
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
hiero-ledger/hiero-sdk-js#4489 ·
メンテナーはふだん 1 日以内に返信
-
[Bug] The clients language filter cannot select the rows the page labels as unknown対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
apache/rocketmq-dashboard#6103 ·
メンテナーはふだん 4 日以内に返信
-
Bug
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
payloadcms/payload#18652 ·
メンテナーはふだん 1 日以内に返信