Skip to content

fix: harden extension installs — crash recovery, corrupted-state UX, pip cache#225

Merged
lightningpixel merged 1 commit into
devfrom
fix/extension-install-hardening
Jul 15, 2026
Merged

fix: harden extension installs — crash recovery, corrupted-state UX, pip cache#225
lightningpixel merged 1 commit into
devfrom
fix/extension-install-hardening

Conversation

@Lorchie

@Lorchie Lorchie commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Why

A user hit No setup.py found for this extension on hunyuan3d-mini (see the Discord help thread): a failed install could leave a half-deleted extension folder behind — the rollback rm silently failed on Windows file locks after already deleting manifest.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

  • Install copies into a unique staging dir (.modly-staging-<id>-<ts>), swaps it into place with retried renames, then runs setup.py/npm install in the final folder under a .modly-incomplete marker (venvs record absolute paths — the folder must not move after setup). Setup failure → previous version restored on the spot.
  • Startup reconciler: purges stale staging dirs; a backup whose extension folder is missing or still marked incomplete is restored (last good copy), otherwise discarded. Verified live against all four crash states (kill between renames, kill mid-setup, stale staging, stale backup).
  • Shared fsWithRetry/rmWithRetry/renameWithRetry (EBUSY/EPERM/EACCES + progressive backoff) replace the silent rm calls; model:delete unified on the same helper.

Broken folders become actionable

  • extensions:list flags unloadable folders as corrupted with a manifestError cause (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).
  • Drawer shows a per-cause Corrupted installation banner with a Delete broken folder action; Repair is hidden when it can't help. extensions:uninstall now accepts arbitrary broken folder names (root-confined via resolvePathWithinRoot, which now also rejects ''/'.') and always returns {success,error}. ModelsPage surfaces uninstall failures instead of silently closing the dialog.

Bandwidth (#223)

  • PIP_CACHE_DIRdependencies/pip-cache for every setup.py run, and the setup launcher strips --no-cache-dir from pip commands. A retried install reuses the multi-GB torch wheels instead of re-downloading them.

Testing

  • tsc --noEmit, eslint clean; 23 py + 77 node tests green (new unit tests for staging/backup path helpers and root-escape guards)
  • Reconciler exercised live on all four crash states (restore / discard / purge / no-op)
  • pip-cache launcher exercised end-to-end with the real embedded Python: --no-cache-dir stripped, cache populated, package installed

…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
lightningpixel merged commit 199b1dc into dev Jul 15, 2026
@lightningpixel
lightningpixel deleted the fix/extension-install-hardening branch July 15, 2026 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants