Skip to content

danger ci fails with FetchError: Premature close (ERR_STREAM_PREMATURE_CLOSE) on Node 24.17.0+ #1515

Description

@sakkadak

Summary

On Node 24.17.0+, every danger ci run against the GitHub API fails during DSL assembly while fetching the PR file list / diff / commits — before the Dangerfile is evaluated:

Failed to fetch GitHub pull request files: FetchError: Invalid response body while trying to fetch https://<host>/api/v3/repos/<owner>/<repo>/pulls/<n>/files?page=1&per_page=100: Premature close
    at Gunzip.<anonymous> (node_modules/node-fetch/lib/index.js:400:12)
  type: 'system',
  errno: 'ERR_STREAM_PREMATURE_CLOSE',
  code: 'ERR_STREAM_PREMATURE_CLOSE'

It is deterministic: green on Node 24.16.0, red on Node 24.17.0 for the same repo and the same Danger version.

Root cause

This is not a real decode/stream failure — it's a false positive from a Node change interacting with node-fetch@2:

  • Node 24.17.0 (a security release) changed http.Agent so that idle keep-alive sockets in the free socket pool retain a 'data' listener. See http: node-fetch throws ERR_STREAM_PREMATURE_CLOSE on keep-alive socket closures after latest security releases nodejs/node#63989 ("node-fetch throws ERR_STREAM_PREMATURE_CLOSE on keep-alive socket closures after latest security releases") and the fix http: avoid stream listeners on idle agent sockets nodejs/node#64004 ("http: avoid stream listeners on idle agent sockets").
  • node-fetch@2's fixResponseChunkedTransferBadEnding (lib/index.js:1739) only runs for responses sent as Transfer-Encoding: chunked with no Content-Length — which is exactly how the GitHub API returns gzip-encoded JSON. On socket close it decides the connection ended uncleanly via socket.listenerCount('data') > 0 (lib/index.js:1745) and throws a synthetic ERR_STREAM_PREMATURE_CLOSE (:1748). With the new idle-socket listener, that check now fires on perfectly healthy responses.

The Gunzip in the stack trace is incidental — it's just the response body stream the synthetic error gets .destroy()'d onto.

Why this needs addressing in Danger

  • The crashing requests (getPullRequestFiles, getPullRequestDiff, getPullRequestCommits) are issued by Danger core during DSL assembly, before the Dangerfile runs, so consumers have no seam to work around it (no request-option hook; the only fetch DI point is reserved for Peril).
  • node-fetch@2 is effectively frozen at 2.7.0 and won't be patched; node-fetch@3 is ESM-only and can't be require()'d by Danger.
  • The real fix (http: avoid stream listeners on idle agent sockets nodejs/node#64004) is merged but not yet in any released Node line (latest at the time of writing: v24.17.0 / v22.23.0), so "upgrade Node" isn't actionable for affected users today.

Suggested fix

Have Danger's fetch wrapper decline gzip negotiation (compress: false) for its API requests. The server then returns identity-encoded bodies with a Content-Length, so the chunked && !content-length branch never arms. Minimal, backwards-compatible, no dependency change, and easily reverted once a patched Node line ships or the undici migration (#1514) lands.

I'll open a PR with this change plus a regression test.

Environment

Danger 13.x (also affects 11.x / 12.x — all depend on node-fetch@^2)
Node 24.17.0+ (and the corresponding 22.x security release line)
Platform GitHub / GitHub Enterprise

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions