fix(install): retry download body stream, verification, and extraction#1719
Merged
Conversation
CI intermittently failed with three different errors that share one root cause: the HTTP retry logic wrapped only the request setup (`send().await?.error_for_status()`), which returns as soon as the response headers arrive. The body streaming to disk, hash verification, and tar/archive extraction all happened *after* the retry closure returned, so a truncated or corrupt download surfaced immediately as `UnexpectedEof` (tar extraction), `HashMismatch` (verification), or a failed node download — none of which triggered a retry. Affected flaky tests: - vite_install: test_detect_package_manager_with_yarn_config_cjs (UnexpectedEof) - vite_install: test_download_success_package_manager_with_sha1_and_sha224 (HashMismatch) - vite_js_runtime: test_execute_node_version (download failed) Fix: - request.rs: `download_file` now retries the request + body stream as a single unit and detects truncation (bytes written != advertised Content-Length). `download_and_extract_tgz_with_hash` retries the full download → verify → extract pipeline on transient content-integrity errors (Io / HashMismatch), resetting the target dir each attempt. Network errors stay inside `download_file`'s retry and the 404 -> PackageManagerVersionNotFound mapping is preserved. - download.rs (node): `download_file` retries request + body stream with the same Content-Length truncation check. - runtime.rs (node): wraps download → verify → extract in a content-integrity retry mirroring the install path, so a complete-but-corrupt node archive is re-downloaded instead of aborting. Adds deterministic httpmock regression tests proving a corrupt archive and a hash mismatch are retried by the full pipeline.
✅ Deploy Preview for viteplus-preview canceled.
|
Member
Author
|
@cursor review |
Cursor Bugbot flagged retry multiplication: a truncated download surfaced as `Error::Io` from `download_file`'s own retry, and the outer `download_and_extract_tgz_with_hash` retry also matched `Error::Io`, so a persistent failure could trigger up to 4×4 = 16 download attempts. Collapse to a single retry layer: `_once` now downloads with a no-retry client (`HttpClient::with_config(0, 0)`), and the pipeline retry owns all resilience. The predicate (`is_retryable_download_error`) retries transient network/HTTP errors (except 404), truncated downloads, corrupt-archive extraction, and hash mismatches; a 404 and permanent config errors fail fast and propagate unchanged so the caller still maps 404 → PackageManagerVersionNotFound (now without wasted retries).
Member
Author
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 0183c8f. Configure here.
cpojer
approved these changes
Jun 1, 2026
Merged
fengmk2
added a commit
that referenced
this pull request
Jun 1, 2026
Release vite-plus v0.1.24. A new `vp pm stage` publishing workflow, hardened installs and upgrades, a Node-version mismatch reinstall prompt, and the bundled vite/vitest/tsdown stack moves forward. ### Features - `vp pm stage`: a new `vp pm` subcommand exposing npm's staged-publishing workflow (upload a build to a staging area without 2FA, then approve or reject it from a trusted device); it maps to `pnpm stage` / `npm stage` / `yarn npm ... --staged` per package manager, with an npm fallback for yarn Classic and bun ([#1715](#1715)), by @fengmk2 - `vp`: prompt to reinstall when up-to-date global packages were built against a different Node.js than the active one (defaults to no); adds `--reinstall-node-mismatch` and `--ignore-node-mismatch`, and skips the prompt in CI ([#1666](#1666)), by @liangmiQwQ - `vp format`: add `format` as a visible alias of `vp fmt`, so the common slip `vp format` resolves correctly and `vp format --init` / `--migrate` apply the same `vite.config.ts` wiring as `vp fmt` ([#1727](#1727)), by @semimikoh ### Fixes & Enhancements - `vp install` / Node runtime download: HTTP retries now wrap the whole body stream, hash verification, and archive extraction (not just the request headers), so truncated or corrupt downloads of package managers and Node are re-fetched instead of failing on the first attempt ([#1719](#1719)), by @fengmk2 - `vp upgrade --force` on Windows: install into a fresh directory before repointing `current`, so the forced reinstall no longer fails trying to overwrite the running `vp.exe` ([#1714](#1714)), by @fengmk2 - `vp install -g`: install global packages directly into their final prefix instead of a temp dir that gets moved, so packages whose postinstall scripts bake in absolute or relative temp paths still resolve their bins; a failed package in a multi-package install no longer removes the shims of the ones that already succeeded ([#1698](#1698)), by @liangmiQwQ - `vp why`: remove the `-g` / `--global` flag, which delegated to the package manager's global mode and ignored Vite+-managed global packages; `vp why` stays project-scoped while `vp outdated -g` keeps using the managed global flow ([#1720](#1720)), by @liangmiQwQ - Windows installer: remove the existing `current` link via PowerShell (detecting junctions, symlinks, and stale directories) instead of `cmd /c rmdir`, which could fail with "The directory is not empty" ([#1726](#1726)), by @TheAlexLichter - `vp create`: skip editor-config detection and package-local editor settings by default when creating a project inside an existing monorepo; `--editor <name>` stays an explicit opt-in and `--no-editor` an opt-out ([#1729](#1729)), by @jong-kyung - `vp create vite:monorepo` (pnpm): keep the aliased `vite`/`vitest` in the website app's `package.json` so the workspace `overrides.vite: catalog:` has a direct consumer and `vp why vite` resolves to `@voidzero-dev/vite-plus-core`; npm/yarn/bun still drop the dead-weight keys ([#1728](#1728)), by @fengmk2 - `vp pack`: rewrite direct `createRequire(...)("picomatch")` calls in bundled tsdown output to the local bundled CJS entry, so packing no longer depends on an undeclared runtime `picomatch` under pnpm `hoist: false` ([#1732](#1732)), by @fengmk2 - `vp migrate`: resolve a `catalog:` husky pin from the workspace catalog (`pnpm-workspace.yaml`, `.yarnrc.yml`, or `package.json` catalogs) during the git-hooks preflight, so a compatible catalog-pinned husky no longer triggers a false "could not determine husky version" warning and skips hook setup ([#1710](#1710)), by @fengmk2 ### Docs - Add a **Copy Prompt** button to the docs site that copies an AI-friendly getting-started prompt (intro, `llms-full.txt` pointer, install commands, and core `vp` commands) for handing straight to a coding agent ([#1706](#1706)), by @fengmk2 - Update `troubleshooting.md`: `vite.config.ts` related issues are resolved by updating oxlint and oxfmt ([#1708](#1708)), by @leaysgur - Clarify the product and repository documentation locations and the new Run guide/config paths in `AGENTS.md` ([#1707](#1707)), by @leaysgur ### Chore - `vp` install: reduce retained `vp` versions from 5 to 3 across the installer, `vp upgrade`, and the shell/PowerShell bootstrap scripts (active and previous versions stay protected for rollback); document the 3-version retention and `vp upgrade --rollback` ([#1716](#1716)), by @fengmk2 - Exclude the snap-tests directory from Vitest config discovery so the VS Code Vitest extension stops generating a stray `.vitest-plugin-loaded` file ([#1723](#1723)), by @liangmiQwQ - Refresh trusted stack stats on the docs homepage ([#1734](#1734)), by @voidzero-guard[bot] - Update @wan9chi's GitHub handle (formerly `branchseer`) ([#1705](#1705)), by @wan9chi - Update GitHub Actions ([#1724](#1724), [#1730](#1730)), by @renovate[bot] - Upgrade upstream dependencies: vite `8.0.14 → 8.0.16`, vitest `4.1.7 → 4.1.8`, tsdown `0.22.0 → 0.22.1`, `@vitejs/devtools` `0.2.0 → 0.3.1` ([#1713](#1713), [#1735](#1735), [#1737](#1737)), by @voidzero-guard[bot] ### Bundled Versions | Tool | Version | Source | | --- | --- | --- | | vite | `8.0.16` | [`f94df87`](vitejs/vite@f94df87) | | rolldown | `1.0.3` | [`a287faa`](rolldown/rolldown@a287faa) | | tsdown | `0.22.1` | [npm](https://npmx.dev/package/tsdown/v/0.22.1) | | vitest | `4.1.8` | [npm](https://npmx.dev/package/vitest/v/4.1.8) | | oxlint | `1.67.0` | [npm](https://npmx.dev/package/oxlint/v/1.67.0) | | oxlint-tsgolint | `0.23.0` | [npm](https://npmx.dev/package/oxlint-tsgolint/v/0.23.0) | | oxfmt | `0.52.0` | [npm](https://npmx.dev/package/oxfmt/v/0.52.0) | ### New Contributors Welcome to our new contributor @semimikoh! 🎉 **Full Changelog**: v0.1.23...v0.1.24 Merging this PR will trigger the release workflow. --------- Co-authored-by: voidzero-guard[bot] <278573678+voidzero-guard[bot]@users.noreply.github.com> Co-authored-by: MK <fengmk2@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
CI intermittently failed with three different errors that share one root cause: the HTTP retry logic wrapped only the request setup (
send().await?.error_for_status()), which returns as soon as the response headers arrive. The body streaming to disk, hash verification, and tar/archive extraction all happened after the retry closure returned, so a truncated or corrupt download surfaced immediately and was never retried.Flaky tests observed across recent runs:
test_detect_package_manager_with_yarn_config_cjsTarError UnexpectedEoftest_download_success_package_manager_with_sha1_and_sha224HashMismatchtest_execute_node_versionFix
crates/vite_install/src/request.rsdownload_file: the request and the body stream are now a single retried unit, plus a Content-Length truncation check (bytes written != advertised length → error → re-download).download_and_extract_tgz_with_hash: wraps download → verify → extract in a content-integrity retry (is_retryable_extract_errorretries onlyIo/HashMismatch; it deliberately excludesReqwest— already retried insidedownload_file— so the caller's404 → PackageManagerVersionNotFoundmapping is preserved). Each attempt resets the target dir.crates/vite_js_runtime/src/download.rs(node)download_file: same network-layer fix — request + body stream as one retried unit with the Content-Length truncation check.crates/vite_js_runtime/src/runtime.rs(node)download_filesurfaces an exhausted download asDownloadFailed, which the predicate excludes, so there's no retry multiplication.)Tests
httpmockregression tests proving a corrupt archive and a hash mismatch are retried by the full pipeline (they assert the server receives >1 hit; both failed before the fix with "only attempted 1 time(s)").cargo clippy -p vite_install -p vite_js_runtime --all-targets -- -D warnings→ clean.vite_js_runtimelib → 104 passed (incl. the realtest_download_node_integration).test_download_failed_package_manager_with_hashstill returnsHashMismatch.Follow-ups (not in this PR)
vite_shareddownload helper, which would also coverget_bytes/vp upgradeand prevent the next download site from regressing.Content-Length); the hash/extract retry is the backstop there.Note
Medium Risk
Touches core download/install paths for package managers and Node with layered retries; behavior changes (404 handling preserved) but wrong predicates could over-retry or skip needed retries.
Overview
Fixes intermittent install/CI failures where HTTP retries only covered headers (
send+error_for_status), so truncated bodies, hash mismatches, and bad archives failed once with no retry.Network layer:
download_fileinvite_installandvite_js_runtimenow retries request + body write as one unit and errors when bytes written ≠ advertisedContent-Length(Node downloads also reset the progress bar each attempt).Integrity layer: Package-manager tarballs use a single pipeline retry (download → hash → extract) via
download_and_extract_tgz_with_hash_once, withis_retryable_download_error(retries I/O/hash/transient HTTP; not 404). Inner downloads useHttpClient::with_config(0, 0)to avoid nested retries. Node runtime install gets the same pattern withis_retryable_runtime_errorafter SHASUMS are resolved once.Adds httpmock tests asserting corrupt archives and hash mismatches trigger more than one download attempt.
Reviewed by Cursor Bugbot for commit 0183c8f. Configure here.