Skip to content

fix: use HTTP/1.1 for downloads - #3382

Open
gengjiawen wants to merge 1 commit into
nodejs:mainfrom
gengjiawen:fix/download-http1
Open

gengjiawen wants to merge 1 commit into
nodejs:mainfrom
gengjiawen:fix/download-http1

Conversation

@gengjiawen

Copy link
Copy Markdown
Member
Checklist
  • npm install && npm run lint && npm test passes
  • tests are included
  • commit message follows commit guidelines
Description of change

Set allowH2: false on both dispatchers in lib/download.js (the plain Agent and the EnvHttpProxyAgent used for --proxy/--cafile/proxy env), going back to HTTP/1.1 as node-gyp used before undici 8.

This is a defensive mitigation, not a proven root-cause fix.

Why

undici 6 and 7 default to allowH2: false; undici 8 (#3330, shipped in v13.0.1) defaults to true, so downloads from nodejs.org have used HTTP/2 since then. Since then, the install › parallel tests on Windows intermittently fail with an HTTP/2-only error that RetryAgent does not retry, e.g. windows-11-arm, windows-latest:

TypeError: terminated
Caused by: Error [ERR_HTTP2_STREAM_ERROR]: Stream closed with error code NGHTTP2_INTERNAL_ERROR

In Jul–Sep this accounted for 4 windows-11-arm and 3 windows-latest failures of the 3.14 - 26.x jobs (out of 69 runs). It never showed up on Linux or macOS.

Evidence

A/B on the fork, same code except for allowH2: instrumented loop replaying the install › parallel test for 25 min per job on windows-11-arm and windows-latest (run 1, run 2):

HTTP/2 HTTP/1.1
rounds with a failed install 4 / 132 0 / 145
failed installs 10 / 735 0 / 1040
  • Two-sided Fisher exact on rounds: p = 0.050, i.e. borderline, not conclusive on its own.
  • HTTP/2 failures occurred on both x64 and arm64, as terminated ← UND_ERR_RES_CONTENT_LENGTH_MISMATCH. No HTTP/1.1 failures at all.
  • No consistent difference in round duration between the two.
  • Mechanism not established. I first suspected slow tar extraction stalling the stream until the CDN resets it, but the data doesn't support that: HTTP/2 downloads with 80 s+ gaps succeeded, and one failed with a 1 s max gap. Deliberately stalling a download for 180 s from the Windows runners did not reproduce it either.
Tests

The two HTTPS tests in test-download.js now use http2.createSecureServer({ allowHTTP1: true }) and assert req.httpVersion === '1.1'. On main both fail with '2.0' !== '1.1', so they cover the custom-CA path and the proxy tunnel. The plain-Agent path can't be exercised against a local TLS server without a trusted CA. I checked manually that it negotiates http/1.1 with nodejs.org.

Not covered here

If this lands, the check is whether terminated failures disappear from Windows CI; if they keep showing up, HTTP/2 wasn't the cause and this should be revisited.

undici 8 (nodejs#3330) negotiates HTTP/2 by default, so since v13.0.1 header
and node.lib downloads from nodejs.org use HTTP/2. Since then the
parallel install tests on Windows intermittently fail with
"TypeError: terminated" (ERR_HTTP2_STREAM_ERROR or
UND_ERR_RES_CONTENT_LENGTH_MISMATCH), which RetryAgent does not retry.

As a defensive mitigation, go back to HTTP/1.1, as node-gyp used before
undici 8, for both the direct and the proxy/custom CA dispatchers. The
root cause of the truncated HTTP/2 responses is not established.

The HTTPS download tests now use a server that also offers HTTP/2 and
assert that HTTP/1.1 was negotiated.
@cclauss

cclauss commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

This approach seems to move backwards instead of forwards. Is it a short term solution or do we expect to leave this in place?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants