Skip to content

Commit af8ccdc

Browse files
author
User
committed
fix: handle EPIPE errors in StdioServerTransport
Add error handling for stdout write failures to prevent uncaught EPIPE exceptions when clients disconnect unexpectedly. Changes: - Add stdout error listener in start() to catch EPIPE and other errors - Trigger graceful close when stdout errors occur - Handle write errors in send() by rejecting the promise - Clean up stdout error listener in close() This prevents the Node.js process from crashing when a client disconnects while the server is writing to stdout. Fixes #1564
1 parent 65f5f72 commit af8ccdc

File tree

1 file changed

+25
-2
lines changed

1 file changed

+25
-2
lines changed

packages/server/src/server/stdio.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ export class StdioServerTransport implements Transport {
3737
_onerror = (error: Error) => {
3838
this.onerror?.(error);
3939
};
40+
_onstdouterror = (error: Error) => {
41+
// Handle stdout errors (e.g., EPIPE when client disconnects)
42+
// Trigger close to clean up gracefully
43+
this.close().catch(() => {
44+
// Ignore errors during close
45+
});
46+
this.onerror?.(error);
47+
};
4048

4149
/**
4250
* Starts listening for messages on `stdin`.
@@ -51,6 +59,7 @@ export class StdioServerTransport implements Transport {
5159
this._started = true;
5260
this._stdin.on('data', this._ondata);
5361
this._stdin.on('error', this._onerror);
62+
this._stdout.on('error', this._onstdouterror);
5463
}
5564

5665
private processReadBuffer() {
@@ -72,6 +81,7 @@ export class StdioServerTransport implements Transport {
7281
// Remove our event listeners first
7382
this._stdin.off('data', this._ondata);
7483
this._stdin.off('error', this._onerror);
84+
this._stdout.off('error', this._onstdouterror);
7585

7686
// Check if we were the only data listener
7787
const remainingDataListeners = this._stdin.listenerCount('data');
@@ -87,12 +97,25 @@ export class StdioServerTransport implements Transport {
8797
}
8898

8999
send(message: JSONRPCMessage): Promise<void> {
90-
return new Promise(resolve => {
100+
return new Promise((resolve, reject) => {
91101
const json = serializeMessage(message);
102+
103+
// Handle write errors (e.g., EPIPE when client disconnects)
104+
const onError = (error: Error) => {
105+
this._stdout.off('error', onError);
106+
reject(error);
107+
};
108+
109+
this._stdout.once('error', onError);
110+
92111
if (this._stdout.write(json)) {
112+
this._stdout.off('error', onError);
93113
resolve();
94114
} else {
95-
this._stdout.once('drain', resolve);
115+
this._stdout.once('drain', () => {
116+
this._stdout.off('error', onError);
117+
resolve();
118+
});
96119
}
97120
});
98121
}

0 commit comments

Comments
 (0)