Summary
node-pre-gyp install makes exactly one attempt to download a pre-built binary. Any non-2xx response other than 403 fails immediately, so a transient server-side error (a 503 from S3, a 504 from a CDN, a 429 rate limit) forces a full source compile even though the same request would very likely succeed a second later.
There is no retry, backoff, or timeout anywhere in the download path.
Observed behaviour
Installing muhammara@6.0.5 (which downloads from GitHub releases) during a Docker build, with node-pre-gyp@2.0.3:
.../node_modules/muhammara install: [info] using node-pre-gyp@2.0.3
.../node_modules/muhammara install: [info] using node@24.19.0 | linux | x64
.../node_modules/muhammara install: [info] check checked for "/app/node_modules/.../muhammara.node" (not found)
.../node_modules/muhammara install: [log] GET https://github.com/julianhille/MuhammaraJS/releases/download/6.0.5/node-v137-linux-x64-glibc.tar.gz
.../node_modules/muhammara install: [error] install response status 504 Gateway Time-out on https://github.com/julianhille/MuhammaraJS/releases/download/6.0.5/node-v137-linux-x64-glibc.tar.gz
.../node_modules/muhammara install: [warn] Pre-built binaries not installable for muhammara@6.0.5 and node@24.19.0 (node-v137 ABI, glibc) (falling back to source compile with node-gyp)
.../node_modules/muhammara install: [warn] Hit error response status 504 Gateway Time-out on https://github.com/julianhille/MuhammaraJS/releases/download/6.0.5/node-v137-linux-x64-glibc.tar.gz
The 504 arrived roughly 11 seconds after the request was issued, and the install fell straight through to compiling from source.
Impact
The whole point of a pre-built binary is to avoid a toolchain-dependent source build. A single transient gateway error defeats that:
- In CI and Docker builds, the fallback compile adds minutes to every affected install, or fails outright where no compiler or Python is present in the image.
- With
--fallback-to-build=false, the install fails entirely rather than degrading.
Second, related defect: the fallback diagnostic is dead code
print_fallback_error in lib/install.js branches on err.statusCode to choose between two messages. The status-aware branch produces the more useful output:
if (err.statusCode !== undefined) {
full_message = 'Pre-built binaries not found for ' + ...
log.warn('Tried to download(' + err.statusCode + '): ' + opts.hosted_tarball);
...
} else {
full_message = 'Pre-built binaries not installable for ' + ...
log.warn('Hit error ' + err.message);
}
However, the error thrown for a non-2xx response is a bare new Error(...) with no statusCode property:
throw new Error(`response status ${res.status} ${res.statusText} on ${sanitized}`);
So every HTTP failure takes the else branch, including a plain 404, which is the most common real-world case. The Tried to download(<status>) line never appears for an HTTP error, and users see "not installable" where "not found" was intended. This is visible in the trace above: a 504 reported as Hit error rather than Tried to download(504).
Only the 403-without-credentials error and raw S3 SDK errors currently set statusCode.
Expected behaviour
Transient failures should be retried a small, bounded number of times with backoff before falling back to a source build. Failures that cannot be fixed by retrying should continue to fall through immediately. That matters most for 404, which means there is genuinely no binary for this platform and ABI: it is the common path, and adding delay there would slow down every affected install.
Environment
- node-pre-gyp 2.0.3
- Node.js 24.19.0, linux x64, glibc
- Binary host: GitHub releases
- Reproduces against any host returning a transient 5xx; not specific to
muhammara
Summary
node-pre-gyp installmakes exactly one attempt to download a pre-built binary. Any non-2xx response other than 403 fails immediately, so a transient server-side error (a 503 from S3, a 504 from a CDN, a 429 rate limit) forces a full source compile even though the same request would very likely succeed a second later.There is no retry, backoff, or timeout anywhere in the download path.
Observed behaviour
Installing
muhammara@6.0.5(which downloads from GitHub releases) during a Docker build, withnode-pre-gyp@2.0.3:The 504 arrived roughly 11 seconds after the request was issued, and the install fell straight through to compiling from source.
Impact
The whole point of a pre-built binary is to avoid a toolchain-dependent source build. A single transient gateway error defeats that:
--fallback-to-build=false, the install fails entirely rather than degrading.Second, related defect: the fallback diagnostic is dead code
print_fallback_errorinlib/install.jsbranches onerr.statusCodeto choose between two messages. The status-aware branch produces the more useful output:However, the error thrown for a non-2xx response is a bare
new Error(...)with nostatusCodeproperty:So every HTTP failure takes the
elsebranch, including a plain 404, which is the most common real-world case. TheTried to download(<status>)line never appears for an HTTP error, and users see "not installable" where "not found" was intended. This is visible in the trace above: a 504 reported asHit errorrather thanTried to download(504).Only the 403-without-credentials error and raw S3 SDK errors currently set
statusCode.Expected behaviour
Transient failures should be retried a small, bounded number of times with backoff before falling back to a source build. Failures that cannot be fixed by retrying should continue to fall through immediately. That matters most for 404, which means there is genuinely no binary for this platform and ABI: it is the common path, and adding delay there would slow down every affected install.
Environment
muhammara