Windows: conin socket has no 'error' listener, so a failed pty write is an uncaught exception

Open Beginner friendly
#942 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
node.js, typescript

Research direction

Start in windowsPtyAgent.js:79, where conin creates _inSocket, and compare it with the conout error handling in windowsTerminal.js:54 and :90. Trace the write and cleanup paths at windowsTerminal.js:123 and windowsPtyAgent.js:176, then add coverage for failed writes or destruction during a write on Windows. Done means conin errors no longer become uncaught process exceptions and existing exit or close behavior remains intact.

Written by the indexing model from the issue text.

Description

Summary

On Windows, the conin (write side) socket is constructed without an 'error' listener, so any write failure on it becomes an uncaught exception and takes down the host process. The conout (read side) socket does get one.

Observed as Error: write EAGAIN (errno: -4088) escaping as an uncaught exception, with no 'error' event available on anything the embedder owns.

Version: 1.2.0-beta.14 (line references below are from the published lib/, and match the compiled output 1:1).

The asymmetry

conout gets a listener — windowsTerminal.js:54 assigns it and :90 attaches the handler:

this._socket = this._agent.outSocket;
// ...
this._socket.on('error', (err) => { /* ... */ });

conin does not. windowsPtyAgent.js:79:

const inSocketFD = fs.openSync(term.conin, 'w');
this._inSocket = new net.Socket({ fd: inSocketFD, readable: false, writable: true });
this._inSocket.setEncoding('utf8');

Grepping the package, _inSocket never receives an 'error' handler anywhere.

Why that is load-bearing

_inSocket is the write path — windowsTerminal.js:123:

this._agent.inSocket.write(data);

and kill() destroys it — windowsPtyAgent.js:176:

this._inSocket.destroy();

A net.Socket with no 'error' listener re-throws on error, so both of these can crash the process:

  • backpressure — EAGAIN when conin's buffer fills under sustained input
  • a write racing kill() — the socket is destroyed while a write is in flight

Because the throw originates inside node-pty's own plumbing, an embedder cannot prevent it. Terminal.on() forwards to the conout socket, so there is no public route to conin; the only workaround is reaching through the private _agent to inSocket and attaching the listener from outside, which is what we currently do.

Suggested fix

Attach a handler where the socket is created, mirroring the conout side — even a no-op prevents the process-level throw and lets the existing exit/close paths drive the lifecycle:

this._inSocket.on('error', () => { /* surfaced via onExit / the existing error path */ });

Routing it through _onError for parity with conout would be better still, if that suits the intended semantics.

Impact

For anything long-lived that writes to a pty and kills ptys on demand — terminal multiplexers, session managers, agent supervisors — this is a hard crash of the host process on Windows rather than a recoverable error. It presents as an intermittent uncaught exception attributed to whatever code happened to be running, which makes it easy to misfile as unrelated flakiness.

Happy to open a PR if the one-line form above is acceptable.

Dominant language
TypeScript
Stars
2k
Forks
337
Avg merge
21h 58m
Merged PRs (30d)
3

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from microsoft/node-pty

All issues in microsoft/node-pty

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.