Response body read errors bypass the SDK's typed error handling
@radenkovic is already working on this.
Since Jul 28, 2026.
- Dominant language
- TypeScript
- Stars
- 181
- Forks
- 126
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 1
Description
Describe the bug
When the HTTP connection drops while the SDK is reading a response body, the error that reaches my code is a raw TypeError: terminated from Node's fetch stack, not one of the SDK's own error types (NylasApiError, NylasOAuthError, NylasSdkTimeoutError). My catch block checks instanceof NylasApiError, so it falls through and I lose the request context I normally rely on (the requestId and flow id).
Looking at src/apiClient.ts, the two response-body reads are outside any try/catch:
requestWithResponsereads the body at line 317:const text = await response.text();. Thetryblock starts on the next line and only wrapsJSON.parse(text), so a failure inresponse.text()itself is not caught here.requestRawreads the body at line 348:const arrayBuffer = await response.arrayBuffer();, with no try/catch in the function.
This looks inconsistent with sendRequest, which already wraps fetch() and converts failures into SDK errors (for example AbortError becomes NylasSdkTimeoutError, and a non-2xx status becomes NylasApiError). A connection that fails before the first byte surfaces a typed error, but the same connection failing mid-body surfaces a raw undici error. The requestRaw path is the one I hit most, since it backs Attachments.download() and large downloads over a flaky network are the most likely to be cut off partway.
To Reproduce
Point the client at a local server that returns a success status and then closes the socket mid-body, so the body read rejects:
const http = require('http');
const Nylas = require('nylas').default;
// Server that sends headers + a partial body, then destroys the socket
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write('{"request_id":"abc","data":'); // intentionally incomplete
res.socket.destroy(); // drop the connection mid-body
});
server.listen(0, async () => {
const nylas = new Nylas({ apiKey: 'test', apiUri: `http://localhost:${server.address().port}` });
try {
await nylas.messages.list({ identifier: 'me' });
} catch (e) {
console.log('name:', e.name); // TypeError
console.log('message:', e.message); // terminated
console.log('is NylasApiError:', e instanceof Nylas.NylasApiError); // false
} finally {
server.close();
}
});
The same happens through nylas.attachments.download(...), which reads the body with response.arrayBuffer().
Expected behavior
A failure while reading the response body should be surfaced the same way as other transport failures: as an SDK error type, carrying the flow id / request id when available, so that a catch (e) { if (e instanceof NylasApiError) ... } block behaves consistently regardless of when in the request lifecycle the connection dropped. At minimum the raw error should be wrapped in a descriptive Error rather than leaking terminated.
SDK Version:
8.4.0
Additional context
The two spots are requestWithResponse (src/apiClient.ts:317) and requestRaw (src/apiClient.ts:348). Wrapping each body read in a try/catch and rethrowing an SDK-typed error, matching the pattern already used in sendRequest and in the existing JSON.parse catch, would keep the error surface consistent. Happy to open a PR with that change if it would be useful.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.