fe1a8ad5f0
## 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](https://github.com/twentyhq/twenty/pull/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](https://github.com/danger/danger-js/issues/1515), [nodejs/node#63989](https://github.com/nodejs/node/issues/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](https://github.com/danger/danger-js/pull/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. -->
18 lines
1.1 KiB
Diff
18 lines
1.1 KiB
Diff
diff --git a/distribution/api/fetch.js b/distribution/api/fetch.js
|
|
index 2814d327173abcaf0ee4e162716806c1e2840956..3d1bc2ace274966ac23f865554f0cb027e10930e 100644
|
|
--- a/distribution/api/fetch.js
|
|
+++ b/distribution/api/fetch.js
|
|
@@ -168,6 +168,12 @@ function api(url, init, suppressErrorReporting, processEnv) {
|
|
var secure = url.toString().startsWith("https");
|
|
init.agent = secure ? new https_proxy_agent_1.HttpsProxyAgent(proxy) : new http_proxy_agent_1.HttpProxyAgent(proxy);
|
|
}
|
|
+ // Decline gzip so node-fetch@2 doesn't false-positive ERR_STREAM_PREMATURE_CLOSE
|
|
+ // on Node 24.17.0+ (keep-alive idle-socket 'data' listener misread as unclean close).
|
|
+ // Backport of danger/danger-js#1516 — drop this patch once it ships in a release.
|
|
+ if (init.compress === undefined) {
|
|
+ init.compress = false;
|
|
+ }
|
|
return retryableFetch(url, init).then(function (response) { return __awaiter(_this, void 0, void 0, function () {
|
|
var clonedResponse, responseBody, responseJSON, e_1;
|
|
return __generator(this, function (_a) {
|