microsoft / microsoft/TypeScript

Bad tsserverPath in the unstable/sync API client surfaces as bare "EPIPE: broken pipe, write" instead of naming the executable

Open
#63,885 0 comments 0 reactions 0 assignees View on GitHub
Possible Improvement
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

### Version

- `typescript@7.0.2` (`gitHead` `2bd066d87f5bafd315be9f40889d0a60b9e58e0b`),
`@typescript/typescript-linux-x64@7.0.2`
- Node v22.22.0, Linux x86_64 (Fedora 43)

### Repro

```js
import { API } from "typescript/unstable/sync";

const api = new API({ tsserverPath: "/nonexistent/binary" });
api.parseConfigFile("/some/tsconfig.json");
```

### Expected

An error identifying the executable, along the lines of the default path's
`Executable not found: `, or the channel's own
`Unexpected EOF while reading from child process (exited with code N)`.

### Actual

```
Error: EPIPE: broken pipe, write
at writeSync (node:fs:922:3)
at SyncRpcChannel.writeAllBuf (.../dist/api/syncChannel.js:495:27)
at SyncRpcChannel.writeTuple (.../dist/api/syncChannel.js:314:18)
at SyncRpcChannel.requestBytesSync (.../dist/api/syncChannel.js:221:14)
at Client.apiRequest (.../dist/api/sync/client.js:58:37)
```

The same `EPIPE` appears for any `tsserverPath` that is not an API server —
`/bin/cat` and `/bin/sleep` both produce it — so the message never distinguishes
"executable missing" from "executable is not a tsgo API server".

### Analysis

- `resolveExePath` (`dist/api/options.js`) returns `options.tsserverPath`
unchecked: `return options.tsserverPath ?? getExePath();`. The default branch,
`getExePath` (`lib/getExePath.js`), does `fs.existsSync(exe)` and throws
`Executable not found: ` — the explicit-path branch has no such check.
- `SyncRpcChannel.writeAllBuf` (`dist/api/syncChannel.js`) catches only
`EAGAIN`/`EWOULDBLOCK` and rethrows everything else raw. The read side has an
`eofError()` helper that reports the child's `exitCode`/`signalCode`; the write
side has no equivalent, so a child that died before the first request produces
the low-level errno instead.
- The `spawn` `"error"` event (`ENOENT`) is never observed on the child.

A check in `resolveExePath` mirroring `getExePath`'s `existsSync`, plus an
`EPIPE` branch in `writeAllBuf` that raises the channel's `eofError()`, would
cover both shapes.

Contributor guide

Open the contributing guide

Research direction

Start with resolveExePath in dist/api/options.js and compare it with getExePath in lib/getExePath.js, then inspect SyncRpcChannel.writeAllBuf and eofError in dist/api/syncChannel.js. Re-run the supplied sync API example with /nonexistent/binary and a non-server executable; done means failures identify the executable or child-process exit instead of exposing a bare EPIPE.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.