Skip to main content
← Back to list
01Issue
BugShippedSwamp CLIPublic
Assigneeshammz

Relationships

#2724 An extension upgrade that hits a type collision is rolled back to no version: roll back to the prior version with a commit/rollback install handle

Opened by hammz · 9/29/2026· Shipped 9/30/2026

Summary

When an extension upgrade hits a DuplicateTypeError in the catalog save, the rollback leaves no version installed. InstallExtensionService.rollbackOnCollision (src/libswamp/extensions/install_extension_service.ts:372) deletes createdPaths and restores the prior lockfile entry. But createdPaths includes every file written into the extension root, including files that overwrote v1's (see InstallResult.createdPaths, pull.ts ~139). And v1-only files were already pruned as orphans. The lockfile then says v1 is installed while neither v1's nor v2's files are on disk, and the same holds for dependencies installed with it.

With stage and swap (swamp-club#2723), the old roots are kept until commit, so a rollback can put the prior version back exactly.

This is the second half of unit U4 of the swamp-club#2612 split (see the planning ripples there). It affects every repo and ships on its own. #2612 needs it because archives are stored, and superseded archives are deleted, only in commit(). Depends on swamp-club#2723.

Scope

  1. PendingInstall handle returned by applyInstall in place of #2723's commit at the end of apply. It covers the top-level install and every dependency installed with it.
    • commit(): delete each staging folder, including old/, and mark the journal committed. #2612 later adds storing the archive and deleting the superseded archive here.
    • rollback(): reverse each swap, dependencies first and then the top level, in reverse install order. For each entry whose writeEntry ran, restore the prior lockfile entry with every field (include, checksum, filesChecksum, serverUrl, channel, pulledAt), or remove the entry if there was none. Skills fall back to deleting createdPaths as today.
    • Both are idempotent. The journal records committed / rolled-back so #2723's recovery treats them as finished.
  2. installExtension (the direct caller, e.g. the auto-resolver at src/cli/auto_resolver_adapters.ts:309) commits right after apply, as in #2723.
  3. InstallExtensionService.execute holds the handle across phase 8, under the #2709 lock:
    • saveAll succeeds → commit().
    • DuplicateTypeError → rollback(), then the existing user error. rollbackOnCollision is deleted.
    • Other phase-8 faults (bundling in buildExtensionFromDisk, or a generic saveAll failure) → commit() then rethrow with today's "Install partially applied" guidance, as the #2612 plan decided. While implementing, reconsider the bundling failure, which happens before the catalog is touched: rolling back there looks strictly safer. If you change it, say so in the PR.
  4. Lockfile: rollback re-reads with refresh() (#2709) before restoring, and never removes an entry this install did not write.
  5. Upgrade path: UpgradeExtensionService, update, and serve's install handler inherit this through InstallExtensionService. Confirm with a test for each.

Tests

  • Unit: a v1→v2 upgrade whose catalog save throws DuplicateTypeError ends with v1's exact tree, bundles and lockfile entry (including channel and pulledAt). This test fails on main today.
  • Unit: the same with a dependency installed in the same call. The dependency's files and entry are removed; a dependency that was already installed is untouched.
  • Unit: first install plus collision → no files and no entry remain, as today.
  • Unit: commit() and rollback() are idempotent, and recovery (#2723) treats committed and rolled-back journals as finished.
  • Unit: a generic phase-8 fault commits and keeps today's message.
  • Integration: a collision during extension update in a temp repo leaves the repo exactly as it was before the command.

Docs

design/primitives/extensions.md: replace the "FS rollback on DuplicateTypeError" description and the crash-state posture with the commit/rollback handle.

02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 11 MOREREVIEW+ 14 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

9/30/2026, 5:42:33 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
hammz assigned hammz9/30/2026, 12:55:09 PM

Sign in to post a ripple.