Repository navigation
Conversation
7z reports a truncated or unreadable archive as "Cannot open the file as [7z] archive" with "Unexpected end of archive". isFileInUse matched the "cannot open", so extraction was retried three times, isCritical never got to "unexpected end of archive", and queryContinue's terminal check looked for the old "Can not open the file as archive" wording, so the "Archive damaged" dialog offered Continue, which installed nothing. Every way out of it ended as a user cancel. 7z names the archive type only once it has read the file, so that wording is now excluded from the file-in-use match, while the untyped "Cannot open the file as archive" that a locked file produces is still retried. The terminal check matches both wordings, case-insensitively. Fixes LAZ-1287 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Interactive damaged-archive installs can leave start-install-download callers waiting because the callback is not completed.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Updates archive-error classification so truncated .7z files fail as damaged instead of being retried as locked.
Changes:
- Distinguishes typed 7-Zip open failures from file-lock errors.
- Removes Continue for archives that could not be opened.
- Adds regression coverage for corruption, locks, and CRC errors.
| File | Description |
|---|---|
InstallManager.ts |
Refines extraction-error classification and dialog actions. |
InstallManager.archiveErrors.test.ts |
Adds archive-error regression tests. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| return true; | ||
| } | ||
| const lowered = errorMessage.toLowerCase(); | ||
| const lowered = errorMessage.replace(ARCHIVE_UNREADABLE_AS_TYPE, "").toLowerCase(); |
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
A truncated or corrupt
.7z(for example an interrupted download of a large collection member) makes Vortex retry the extraction three times as if the file were locked, then show "Archive damaged" with a Continue button that installs nothing. Every way out ends as a user cancel:finish mod install {outcome: "canceled"}, and in a collectiondependency install failed, requeued for retry {error: "canceled by user"}plus "Installation canceled". Seen in a user's 2.7.2 log (Skyland AIO 4.32.0) and reproduced on master 34f3def. LAZ-1287.7-Zip reports such a file with one error string that holds both
Open ERROR: Cannot open the file as [7z] archiveandERRORS: Unexpected end of archive. InInstallManager:isFileInUsematches its "cannot open", soextractWithRetryretries.isCriticalreturns false as soon asisFileInUsematches, so "unexpected end of archive" never yields anArchiveBrokenError.queryContinue's terminal check looks for the literalCan not open the file as archive, which current 7z never prints, so Continue is always offered.Change
src/renderer/src/extensions/mod_management/InstallManager.ts(+10/−2):isFileInUsenow ignores that typed wording. The untyped "Cannot open the file as archive", which is what an archive held open by another process produces (followed by the system's sharing-violation message), still counts as in use and is still retried. That untyped wording is also what 7z prints for a file it can't identify at all (e.g. a zero-filled.rar), so such a file is still retried three times; only its Continue goes away.isCriticalsees "Unexpected end of archive" and the install fails withArchiveBrokenError, through the existing damaged-archive handling.queryContinue's terminal check matches "Cannot" or "Can not open the file as [type] archive", with or without a type and in any case, so Continue is no longer offered when 7z could not open the archive at all.InstallManager.archiveErrors.test.ts(new): regression tests on the realInstallManager, using 7-Zip 26.00 output captured from real runs. The non-English lock message in one test is a stand-in, not captured output.Behaviour changes
.7z: no retries and no "Archive damaged" dialog. The install fails (outcome: "failed") instead of being canceled, and the download is marked failed. The user gets "Installation failed, archive is damaged" (Delete, Delete & Redownload) and a " failed to install" error. Truncated.zipand.raralready failed this way on master..zip, a text file renamed.7z): no retries; the dialog offers Cancel and Delete, no longer Continue..7znow sends "installation failed" instead of "installation cancelled"; itserror_messagecarries the archive's file name anderror_codeis "unknown".simulateclassifies the same way..zipstill offers Continue.Evidence
In the app (A/B), author: source development build, Windows 11 on ARM under x64 emulation; base renderer from master 34f3def, head 2625473. Disposable Stardew Valley folder, Premium login. Real archive
nxm://stardewvalley/mods/4399/files/121125("Xtardew Valley-4399-3-2-0-1735946829.7z", 1,718,827 B), downloaded by Vortex, truncated in place to 859,413 B, installed withstart-install-download. One run per side.canceled(the user's log)outcome: "canceled"canceled, download staysfinishedfailed, downloadfailed, "archive is damaged" notificationoutcome: "success"In the app (A/B), independent QA with its own real archives (7z mod 1083 file 56424, 15,973,244 B; zip mod 5969 file 99115; rar mod 4542 file 21774), one run each:
"Delete & Redownload" on the new notification re-fetched the file and the install succeeded.
Regression test: 6 tests. With the
InstallManager.tschange reverted, 3 fail (truncated .7z not anArchiveBrokenError; not-an-archive extracted 4 times instead of once; Continue offered after a lasting lock). The lock-retry and CRC-Continue tests pass on both sides. Confirmed by pr-preflight's revert check, by the author and by QA.Scoped suites:
mod_management, 31 files, 354 tests pass. Renderer typecheck and eslint clean. Trial merge with the LAZ-1286 branch: clean, 32 files and 363 tests pass.pr-preflight: 0 fail, 1 warn (callers outside the diff:
isCritical,extractWithRetry,simulate,installInner, each reviewed;util.ts'squeryContinueis an unrelated option), revert check pass.pnpm run verify: not started; it was due after round-2 QA, and work was paused first.E2E: not started, for the same reason.
CI: the fork's checks started when this draft was opened; see the Checks tab.
Review
Round 1 QA on 2625473 by a separate agent, which reproduced the problem independently with its own archives. Nothing blocking. Findings:
ArchiveBrokenErrorbranch ofinstall()'s catch (InstallManager.ts~1932–1987) never callspromiseCallbackwhen the install isn't unattended. Pre-existing for truncated zip/rar, but this change routes a truncated .7z into it:start-install-download … __CALLBACK__never returns ("Installation completed but callback was not called"). Callers left waiting: Mods page Enable on not-yet-installed downloads, collections'did-download-collection, the import notification's "Install All". Collection and dependency installs run unattended and are not affected. A fix with aninstall()-level test was in progress when work paused; it is not on this branch.Classification: 0 preflight, 2 author, 1 new, 2 judgment (plus 2 nits).
Not covered
🤖 Generated with Claude Code
Written with doodlebot.