fix: harden extension installs — crash recovery, corrupted-state UX, pip cache#225
Merged
Merged
Conversation
…pip cache
- Atomic install: copy to a unique .modly-staging-<id>-<ts>, swap into place
with retried renames, then run setup.py in the FINAL folder under a
.modly-incomplete marker (venvs record absolute paths, so the folder must
not move after setup). A failed setup restores the previous version.
- Startup reconciler: purges stale staging dirs, restores the backup when an
extension folder is missing or still marked incomplete, discards it otherwise.
- Shared fsWithRetry/rmWithRetry/renameWithRetry (EBUSY/EPERM/EACCES filter,
progressive backoff) replace the silent rm calls; model:delete unified on it.
- extensions:list flags broken folders as corrupted with manifestError
(missing / invalid / incomplete); in-flight installs are hidden, dot-dirs
skipped. Python registry also skips dot-dirs and marked folders.
- ExtensionDrawer: per-cause 'Corrupted installation' banner with a Delete
broken folder action; Repair hidden when it cannot help. ModelsPage surfaces
uninstall failures instead of silently closing the dialog.
- extensions:uninstall accepts arbitrary broken folder names (root-confined via
resolvePathWithinRoot, which now also rejects '' and '.'), always returns
{success,error} instead of rejecting the invoke.
- pip wheel cache (#223): PIP_CACHE_DIR points at dependencies/pip-cache and
the setup launcher strips --no-cache-dir from pip commands, so a retried
install reuses multi-GB torch wheels instead of re-downloading them.
lightningpixel
approved these changes
Jul 15, 2026
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.
Why
A user hit
No setup.py found for this extensionon hunyuan3d-mini (see the Discord help thread): a failed install could leave a half-deleted extension folder behind — the rollbackrmsilently failed on Windows file locks after already deletingmanifest.json/setup.py. The broken folder then showed up as an empty skeleton card with a Repair button that could only fail. Fixes #223 as well.What
Atomic, crash-safe installs
.modly-staging-<id>-<ts>), swaps it into place with retried renames, then runssetup.py/npm installin the final folder under a.modly-incompletemarker (venvs record absolute paths — the folder must not move after setup). Setup failure → previous version restored on the spot.fsWithRetry/rmWithRetry/renameWithRetry(EBUSY/EPERM/EACCES + progressive backoff) replace the silentrmcalls;model:deleteunified on the same helper.Broken folders become actionable
extensions:listflags unloadable folders ascorruptedwith amanifestErrorcause (missing/invalid/incomplete); dot-dirs skipped; in-flight installs hidden. The Python registry also skips dot-dirs and marked folders (no more loading a mid-install staging dir on reload).extensions:uninstallnow accepts arbitrary broken folder names (root-confined viaresolvePathWithinRoot, which now also rejects''/'.') and always returns{success,error}. ModelsPage surfaces uninstall failures instead of silently closing the dialog.Bandwidth (#223)
PIP_CACHE_DIR→dependencies/pip-cachefor everysetup.pyrun, and the setup launcher strips--no-cache-dirfrom pip commands. A retried install reuses the multi-GB torch wheels instead of re-downloading them.Testing
tsc --noEmit,eslintclean; 23 py + 77 node tests green (new unit tests for staging/backup path helpers and root-escape guards)--no-cache-dirstripped, cache populated, package installed