Summary
pacquet install aborts the whole install when a single tarball fetch hits a transient network failure (connection reset, TLS error, timeout, DNS blip). pnpm retries; pacquet does not.
Repro
During a large install (1352 packages) I hit:
Error: pacquet_tarball::fetch_tarball
× installing dependencies
├─▶ Failed to fetch https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz: error
│ sending request for url (https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-
│ 1.0.1.tgz)
curl -I on that URL returned HTTP/2 200 immediately afterwards, and rerunning pacquet install --frozen-lockfile completed cleanly — the URL was fine, this was a transient reqwest error.
Where
crates/tarball/src/lib.rs:253-259 — the download path makes a single client.get(package_url).send() call and bubbles any reqwest::Error up as TarballError::FetchTarball:
let response = http_client
.run_with_permit(|client| client.get(package_url).send())
.await
.map_err(network_error)?
.bytes()
.await
.map_err(network_error)?;
No retry wrapper on either the send() or the bytes() step.
What pnpm does
pnpm wraps the entire tarball fetch — body download and addFilesFromTarball (integrity check + extraction) — in one retried closure (fetching/tarball-fetcher/src/remoteTarballFetcher.ts). Only HTTP 401, 403, 404 (and the git-prepare error code) fail fast; every other error — arbitrary 4xx, 5xx, network reset, integrity mismatch, gzip / tar parse error — retries with exponential backoff.
Backoff settings come from fetchRetries, fetchRetryFactor, fetchRetryMintimeout, fetchRetryMaxtimeout (defaults 2, 10, 10000, 60000). pnpm 11 reads them only from pnpm-workspace.yaml (camelCase); they are excluded from NPM_AUTH_SETTINGS (config/config/src/auth.ts), so an .npmrc line is silently ignored.
Proposed fix
Port pnpm's remoteTarballFetcher.ts retry shape:
- Wrap network + integrity + extract in one retry boundary (so a flaky transfer that survives TCP framing but fails the SHA-512 hash also recovers via re-fetch).
- Fail fast only on HTTP 401 / 403 / 404; retry everything else.
- Match pnpm's
@zkochan/retry formula: delay = min(min_timeout * factor.pow(attempt), max_timeout), no jitter.
- Read
fetch-retries* from pnpm-workspace.yaml only.
Context: surfaced while testing #258 against a 1352-package lockfile.
Update: Fixed by #301.
Summary
pacquet installaborts the whole install when a single tarball fetch hits a transient network failure (connection reset, TLS error, timeout, DNS blip). pnpm retries; pacquet does not.Repro
During a large install (1352 packages) I hit:
curl -Ion that URL returnedHTTP/2 200immediately afterwards, and rerunningpacquet install --frozen-lockfilecompleted cleanly — the URL was fine, this was a transient reqwest error.Where
crates/tarball/src/lib.rs:253-259— the download path makes a singleclient.get(package_url).send()call and bubbles anyreqwest::Errorup asTarballError::FetchTarball:No retry wrapper on either the
send()or thebytes()step.What pnpm does
pnpm wraps the entire tarball fetch — body download and
addFilesFromTarball(integrity check + extraction) — in one retried closure (fetching/tarball-fetcher/src/remoteTarballFetcher.ts). Only HTTP401,403,404(and the git-prepare error code) fail fast; every other error — arbitrary 4xx, 5xx, network reset, integrity mismatch, gzip / tar parse error — retries with exponential backoff.Backoff settings come from
fetchRetries,fetchRetryFactor,fetchRetryMintimeout,fetchRetryMaxtimeout(defaults2,10,10000,60000). pnpm 11 reads them only frompnpm-workspace.yaml(camelCase); they are excluded fromNPM_AUTH_SETTINGS(config/config/src/auth.ts), so an.npmrcline is silently ignored.Proposed fix
Port pnpm's
remoteTarballFetcher.tsretry shape:@zkochan/retryformula:delay = min(min_timeout * factor.pow(attempt), max_timeout), no jitter.fetch-retries*frompnpm-workspace.yamlonly.Context: surfaced while testing #258 against a 1352-package lockfile.
Update: Fixed by #301.