fix: avoid spurious ERR_STREAM_PREMATURE_CLOSE on Node 24.17.0+ by declining gzip for API requests#1516
fix: avoid spurious ERR_STREAM_PREMATURE_CLOSE on Node 24.17.0+ by declining gzip for API requests#1516sakkadak wants to merge 1 commit into
Conversation
… API requests Node >= 24.17.0 attaches a 'data' listener to idle keep-alive sockets in the http.Agent free pool (the CVE-2026-48931 "response queue poisoning" hardening). node-fetch@2's fixResponseChunkedTransferBadEnding reads socket.listenerCount('data') and misreads that listener as an unclean close, throwing a spurious ERR_STREAM_PREMATURE_CLOSE for chunked responses with no Content-Length (i.e. gzip-encoded GitHub API responses). This breaks every `danger ci` run on Node 24.17.0 while fetching PR files/diff/commits. Declining gzip (compress:false) makes the server return identity-encoded bodies with a Content-Length, so node-fetch's detector never arms. Guarded with === undefined so callers can still opt back into compression. Refs nodejs/node#63989 (report), nodejs/node#64004 (fix). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Upstream Node status (for context): the Node-side fix (nodejs/node#64004) is merged to Net: anyone on the current secure releases (24.17.0 / 22.23.0) — which are effectively mandatory due to CVE-2026-48931 — has no Node-level fix available today, which is the gap this change covers. It can be reverted once a tagged release actually carries the backport. |
…OSE on Node 24 (#22171) ## Problem The `danger-js` check (`twenty-utils:danger:ci`) started failing intermittently with: ``` FetchError: Invalid response body while trying to fetch https://api.github.com/repos/twentyhq/twenty/pulls/<n>/files: Premature close errno: 'ERR_STREAM_PREMATURE_CLOSE' ``` It fails before the Dangerfile even runs, while fetching PR files / diff / commits. The existing retry wrapper ([#22151](#22151)) reduced it but can't absorb longer GitHub-API windows, so checks still go red. ## Root cause Not "node-fetch is old" generically — a specific recent regression: - Node **22.23.0 / 24.17.0** shipped a security fix for CVE-2026-48931 (http.Agent response-queue poisoning) that attaches a `'data'` listener to idle keep-alive sockets. - `node-fetch@2` misreads that listener as an unclean connection close — but only on **gzip-encoded responses without `Content-Length`**, which is exactly what `api.github.com` returns. - The GitHub-hosted runners rolling into the patched Node 24.17.x in recent weeks is why this surfaced now. See [danger/danger-js#1515](danger/danger-js#1515), [nodejs/node#63989](nodejs/node#63989). ## Why this approach - `node-fetch@2` can't be removed downstream — Danger imports it directly, and it's pervasive transitively (gaxios/googleapis). Dropping it is an upstream migration. - We don't want to pin an old Node version. So: bump `danger` 13.0.4 → 13.0.8 and backport [danger/danger-js#1516](danger/danger-js#1516) via a yarn patch — set `compress: false` on Danger's shared `api()` wrapper. GitHub then returns identity-encoded responses with `Content-Length`, and node-fetch's faulty premature-close detector never fires. Negligible bandwidth cost on these small JSON payloads; explicit caller overrides are preserved via an `=== undefined` guard. ## Changes - `packages/twenty-utils/package.json` — `danger` → patched 13.0.8 - `yarn.lock` — registers the `danger@patch:` resolution - `.yarn/patches/danger-npm-13.0.8-48aba2788c.patch` — the `compress: false` fix ## Verification - Patch dry-run applies cleanly against pristine danger 13.0.8 source. - Inspected yarn's materialized patched cache package — the `compress` fix is present in the linked `distribution/api/fetch.js`. - Confirmed the failing calls (`getPullRequestInfo` / `getPullRequestCommits` / `getPullRequestDiff`) all route through `this.api` → the patched wrapper. ## Lifecycle Temporary backport. When #1516 ships in a Danger release, drop the patch and bump to that version (flagged in a comment inside the patch). The existing CI retry wrapper stays as defense-in-depth. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22171?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
|
Superseded by #1514 (released in v13.0.10), which removes |
Fixes #1515
Problem
On Node 24.17.0+, every
danger cirun fails during DSL assembly while fetching the PR files / diff / commits, before the Dangerfile is evaluated:Deterministic: green on Node 24.16.0, red on Node 24.17.0.
Root cause
Node 24.17.0's
http.Agentchange 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'sfixResponseChunkedTransferBadEnding(lib/index.js:1739/1745) inspectssocket.listenerCount('data')to detect an unclean end, and now false-positives forTransfer-Encoding: chunkedresponses with noContent-Length— i.e. gzip-encoded GitHub API responses — throwing a syntheticERR_STREAM_PREMATURE_CLOSE. It is not a real decode error; theGunzipin the trace is just the body stream the synthetic error is destroyed onto.node-fetch@2is frozen andnode-fetch@3is 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 sharedapi()wrapper, alongside the existing proxy-agent handling. The server then returns identity-encoded bodies with aContent-Length, so node-fetch's bad-ending check never arms. This mirrors how the wrapper already centralizes cross-cutting request config (the proxyagent).Backwards compatibility
=== undefinedguard preserves any explicit callercompresssetting.Content-Length. Danger's payloads are small JSON, so the bandwidth difference is negligible.Tests
Extends
source/api/_tests/fetch.test.ts(sameTestServerpattern as the proxy-agent tests):compresstofalsecompressyarn jest source/api/_tests/fetch.test.ts→ 9/9 pass.tsc -p tsconfig.production.json,eslint, andprettier --checkare all clean.Notes / open to feedback
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.Opened as a draft for your review of the approach before marking ready.