fix: avoid spurious ERR_STREAM_PREMATURE_CLOSE on Node 24.17.0+ by declining gzip for API requests

Closed
SA
4mo ago
+31-0

Fixes #1515

Problem

On Node 24.17.0+, every danger ci run fails during DSL assembly while fetching the PR files / diff / commits, before the Dangerfile is evaluated:

FetchError: Invalid response body ... /pulls/<n>/files: Premature close
  at Gunzip.<anonymous> (node_modules/node-fetch/lib/index.js:400)
  errno: 'ERR_STREAM_PREMATURE_CLOSE', code: 'ERR_STREAM_PREMATURE_CLOSE'

Deterministic: green on Node 24.16.0, red on Node 24.17.0.

Root cause

Node 24.17.0's http.Agent change leaves a 'data' listener on idle keep-alive sockets (nodejs/node#63989; fixed in nodejs/node#64004 "http: avoid stream listeners on idle agent sockets"). node-fetch@2's fixResponseChunkedTransferBadEnding (lib/index.js:1739/1745) inspects socket.listenerCount('data') to detect an unclean end, and now false-positives for Transfer-Encoding: chunked responses with no Content-Length — i.e. gzip-encoded GitHub API responses — throwing a synthetic ERR_STREAM_PREMATURE_CLOSE. It is not a real decode error; the Gunzip in the trace is just the body stream the synthetic error is destroyed onto.

node-fetch@2 is frozen and node-fetch@3 is ESM-only, and the Node fix isn't in a released line yet, so the practical place to address this is Danger's own fetch wrapper.

Fix

Set compress: false (guarded with === undefined, so callers can still opt in) in the shared api() wrapper, alongside the existing proxy-agent handling. The server then returns identity-encoded bodies with a Content-Length, so node-fetch's bad-ending check never arms. This mirrors how the wrapper already centralizes cross-cutting request config (the proxy agent).

Backwards compatibility

  • No signature / dependency / Node-floor change; valid on all supported Node versions.
  • The === undefined guard preserves any explicit caller compress setting.
  • Only observable change: requests no longer advertise gzip; responses come back identity + Content-Length. Danger's payloads are small JSON, so the bandwidth difference is negligible.

Tests

Extends source/api/_tests/fetch.test.ts (same TestServer pattern as the proxy-agent tests):

  • defaults compress to false
  • does not override an explicitly provided compress

yarn jest source/api/_tests/fetch.test.ts → 9/9 pass. tsc -p tsconfig.production.json, eslint, and prettier --check are all clean.

Notes / open to feedback

  • This applies to every platform that goes through api() (GitHub / GitLab / BitBucket), not just GitHub. The bug is platform-agnostic, so the broad fix protects them all — but I'm happy to narrow the scope to the GitHub path if you'd prefer.
  • This is an interim shim; it can be reverted once a patched Node line ships or #1514 (undici migration) lands.

Opened as a draft for your review of the approach before marking ready.