fix(cli): extract the Cypress binary with yauzl instead of extract-zip - #33887
Conversation
npm 11.13.0 (bundled with Node 24.16+) stops running the cypress tarball's postinstall lifecycle script during `npm install --legacy-peer-deps ~/cypress/cypress.tgz`, which left the binary missing and broke the kitchensink binary jobs once nvm started resolving Node 24 to 24.16.0. Invoke `cypress install` explicitly so the binary is in place regardless of whether npm runs the tarball's postinstall.
cypress
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Project |
cypress
|
| Branch Review |
mschile/mystifying-kowalevski-3cd77f
|
| Run status |
|
| Run duration | 19m 35s |
| Commit |
|
| Committer | Matthew Schile |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
1
|
|
|
11
|
|
|
1112
|
|
|
2
|
|
|
27304
|
| View all changes introduced in this branch ↗︎ | |
UI Coverage
63.51%
|
|
|---|---|
|
|
27
|
|
|
47
|
Accessibility
99.01%
|
|
|---|---|
|
|
0 critical
3 serious
1 moderate
0 minor
|
|
|
19
|
Tests for review

cypress/e2e/studio/studio-state-management.cy.ts • 1 failed test • app-e2e
| Test | Artifacts | |
|---|---|---|
| Cypress Studio - State Management > remains in studio mode when the test name is changed on the file system and file watching is disabled |
Test Replay
Screenshots
|
|

commands/net_stubbing.cy.ts • 1 flaky test • 5x-driver-chrome:beta
| Test | Artifacts | |
|---|---|---|
| ... > stops waiting when an fetch request is canceled |
Test Replay
|
|

commands/waiting.cy.js • 1 flaky test • 5x-driver-chrome:beta
| Test | Artifacts | |
|---|---|---|
| ... > errors > throws when route is never resolved |
Test Replay
|
|

e2e/origin/config_env_expose.cy.ts • 1 flaky test • 5x-driver-chrome:beta
| Test | Artifacts | |
|---|---|---|
| cy.origin- Cypress.config() > serializable > overwrites different values in secondary if one exists in the primary |
Test Replay
|
|

commands/files.cy.js • 1 flaky test • 5x-driver-chrome:beta
| Test | Artifacts | |
|---|---|---|
| ... > throws when there is an error reading the file |
Test Replay
|
|

issues/28527.cy.ts • 1 flaky test • 5x-driver-chrome:beta
| Test | Artifacts | |
|---|---|---|
| issue 28527 > fails and then retries and verifies about:blank is not displayed |
Test Replay
Screenshots
|
|
The first 5 flaky specs are shown, see all 11 specs in Cypress Cloud.
The 'Ensure Cypress binary is installed' step hung in CI: only Listr's hide-cursor escape made it to the log before the 10m no-output timeout. Setting npm_config_loglevel=silent triggers cypress install's silent renderer (silentRendererCondition in cli/lib/tasks/install.ts), so the unzip runs without a spinner. Also drop the npx layer and invoke the cypress bin directly.
Bump yauzl to v3.x, remove extract-zip, and replace the Node-based unzip fallback in cli/lib/tasks/unzip.ts with a small extractor built on yauzl + fs.createWriteStream. The new extractor preserves Unix file modes, recreates symlinks, refuses entries (and symlink targets) that would escape the destination, and caps in-memory reads of symlink bodies. Drops the temporary CircleCI workaround that manually installed `unzip` and extracted the zip ahead of the binary install — no longer needed once the cypress.tgz built from this branch uses the new extractor.
- Bugfix entry covering the Node 24.16.0+ install regression and the switch off `extract-zip` / `fd-slicer`. - Dependency-update entry for the yauzl bump and extract-zip removal.
…th paths The 'can really unzip' unit test mocks fs-extra globally. On macOS the test passes through to the real `ditto` binary and never hits the Node fallback, but on Linux CI `ditto` is missing so the fallback runs — and extractWithYauzl used fs-extra, whose mocked createWriteStream returned undefined and crashed the pipeline. Swap fs-extra for Node's built-in fs (fs.promises) so the test exercises the same code path on both OSes.
AtofStryker
left a comment
There was a problem hiding this comment.
@mschile do you think it's worth it here to add tests for extract-with-yauzl? I'd like to see some so in the future we fail "faster" though we do have binary unzipping test coverage in a more e2e fashion throughout the test suite
- Switch the fs.promises import to the more idiomatic fs/promises form (PR feedback). - Remove all zipfile event listeners alongside close() in the finish handler so the zipfile object is fully detached when the extract settles (PR feedback). - Add cli/test/lib/tasks/extract-with-yauzl.spec.ts covering: - simple deflated file extraction - directory entries - Unix mode preservation - symlinks (+ following them to read target content) - path traversal rejection - symlink-target-outside-destination rejection - declared symlink-size cap - streamed-size cap when the declared size lies - invalid zip file rejection
AtofStryker
left a comment
There was a problem hiding this comment.
I don't have much besides cursor's comments. Great work on getting this prioritized and fixed!
- Normalize any thrown / rejected value from a per-entry handler into a real Error before passing to `finish`, so a falsy rejection (e.g. Promise.reject(undefined)) no longer slips through and resolves the extraction as a success. - Recognize directory entries that are marked via the Unix S_IFDIR mode bits, not only via a trailing slash on the entry name. Some archivers emit one but not the other; the previous code would have extracted these as zero-byte regular files. - Add tests covering both new behaviors. - Update the changelog entry to reference the related issue.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3a322e0. Configure here.
This comment was marked as resolved.
This comment was marked as resolved.
|
Released in This comment thread has been locked. If you are still experiencing this issue after upgrading to |

cypress installsilently exits 0 mid-extraction on Node.js 24.16.0 #33891Additional details
The
test-binary-against-kitchensink*CircleCI jobs (electron, chrome, firefox) started failing on develop on 2026-05-21 with:The trigger was a Node patch release picked up by
nvm install 24:15.16.0✅not installed❌Root cause
Cypress's
postinstallruns as expected on Node 24.16.0, but the zip-extraction pipeline insideextract-zipsilently truncates after the first compressed entry. Bisected with minimal reproductions, the failure is in this pipeline:yauzl@2.x's read stream usesfd-slicer, which hasn't been updated in 8 years. Something about how itsReadableinteracts with azlibTransform changed in Node 24.16.0:_readis invoked, bytes accumulate in the source's internal buffer, but zero bytes are forwarded intoInflateRaw— and nodata,end,close, orerrorevent fires on any stream. The process exits cleanly with code 0 because nothing keeps the event loop alive, and the post-install reports success despite having extracted only the first file in the archive.The CircleCI failures came from the trixie base image not having the system
unziptool installed, so the Linux job fell back to the Node-based extractor (extract-zip→yauzl@2.x→fd-slicer). macOS (ditto) and most user environments with a working systemunzipwere unaffected.yauzl@3.xdroppedfd-slicerin favor of reading viafs.readdirectly and is unaffected by the regression.The fix
yauzlto^3.3.1and dropextract-zip.cli/lib/tasks/unzip.tswith a small extractor (cli/lib/tasks/extract-with-yauzl.ts) built on yauzl +fs.createWriteStream. The new extractor:+xbit),PATH_MAX(4096 bytes).unzipon Linux,dittoon macOS) are unchanged.Follow-up
Windows currently uses the Node-based extractor as its primary path because
tar.exewasn't available when the original code was written. Windows 10 1803+ has shippedtar.exefor years, so a follow-up task can migrate Windows to match the Linux/macOS pattern of "OS tool primary, Node fallback." That is beyond the scope of this regression fix.Note
High Risk
Changes the Cypress binary extraction path used during
cypress install(including Windows’ primary unzip implementation), so any regressions could break installs across platforms; it also touches archive-handling logic where mistakes can have security implications.Overview
Fixes a Node 24.16+/26.1+ regression where Cypress installation could silently extract only the first file by removing
extract-zipand switching the CLI’s Node-based unzip to a newyauzl-driven extractor.Adds
extract-with-yauzlto safely recreate directories, files, and symlinks while preserving Unix modes, and includes guards against path traversal and out-of-destination symlinks plus a capped in-memory symlink-target read. Updates unzip progress handling to only open the zip for entry counting, bumpsyauzlto^3.3.1, updates/extends unit tests (including new extractor tests), and documents the fix in the CLI changelog.Reviewed by Cursor Bugbot for commit ca5287a. Bugbot is set up for automated code reviews on this repo. Configure here.
Steps to test
test-binary-against-kitchensink,test-binary-against-kitchensink-chrome, andtest-binary-against-kitchensink-firefoxjobs pass on this PR.test-binary-against-recipes*,test-binary-against-cypress-realworld-app, and the macOS/Windows binary jobs still pass.cliunit tests incli/test/lib/tasks/unzip.spec.tspass (yarn workspace cypress test-unit -- unzip.spec.ts).How has the user experience changed?
No user-visible change. The binary that lands on disk is identical; only the extraction path that produced it has changed.
PR Tasks
cypress-documentation?type definitions?