From 2c7a03758e46b30952dc8d7d1e840fc3d77060b5 Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Mon, 25 May 2026 13:31:46 -0500 Subject: [PATCH] fix(installer): preserve custom-source cache when remote unreachable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When git fetch fails against an existing custom-module cache, cloneRepo previously wiped the cache and attempted a fresh clone, which then also failed for the same reason (network down, repo deleted/moved, auth revoked) — leaving the user with no usable cache. With the new quick-update refresh path calling cloneRepo for every cached custom module, this turned transient remote outages into cache loss on every quick-update. - cloneRepo: on fetch failure with an existing cache, keep the previous clone and surface a warning via prompts.log.warn instead of removing the cache. The downstream metadata write uses the existing HEAD. - _refreshRepoCacheOnce: update the comment to reflect that the common "remote unreachable but cache exists" case is now handled inside cloneRepo; warn on the remaining unrecoverable failures so they aren't silent. Tests: 349 passed, 0 failed. --- .../modules/custom-module-manager.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tools/installer/modules/custom-module-manager.js b/tools/installer/modules/custom-module-manager.js index de55bdf5d..e91deb34f 100644 --- a/tools/installer/modules/custom-module-manager.js +++ b/tools/installer/modules/custom-module-manager.js @@ -435,8 +435,12 @@ class CustomModuleManager { } fetchSpinner.stop(`Updated ${displayName}`); } catch { - fetchSpinner.error(`Update failed, re-downloading ${displayName}`); - await fs.remove(repoCacheDir); + // Fetch failed against an existing cache — most often the remote is + // unreachable (network down, repo deleted/moved, auth revoked). + // Preserve the previous clone so re-deploy still works from cached + // content; surface a warning so the user knows the cache is stale. + fetchSpinner.error(`Could not refresh ${displayName} — keeping cached copy`); + await prompts.log.warn(`Custom module ${displayName} was not refreshed (remote unreachable). Using cached copy.`); } } @@ -760,9 +764,14 @@ class CustomModuleManager { pinOverride: metadata.version || undefined, }); CustomModuleManager._refreshedRepoPaths.add(repoPath); - } catch { - // Keep existing cache on refresh failure; caller may still resolve - // module source from the previous clone. + } catch (error_) { + // cloneRepo only throws here for unrecoverable cases (no cache present + // and a fresh clone failed, or an unexpected internal error). The + // common "remote unreachable but cache exists" case is handled inside + // cloneRepo, which preserves the clone and returns normally. Reaching + // this catch means we have no usable cache — surface a warning so the + // failure isn't silent. + await prompts.log.warn(`Refresh of cached custom module at ${path.basename(repoPath)} failed: ${error_?.message || error_}`); } finally { CustomModuleManager._refreshInFlight.delete(repoPath); }