Relationships
#2639 Extension restore drops the lockfile entry's channel and leaks the parent's expectedChecksum into dependency installs
Opened by hammz · 9/28/2026· Shipped 9/28/2026
Summary
Restoring pulled extensions from the lockfile has two correctness bugs. Both affect every repo today, managedConfig or not. This is unit U1 of the swamp-club#2612 split (see the planning ripples there) and has no dependencies.
1. A restore drops the entry's channel
extension install, repo upgrade, serve's extension.install handler and the auto-resolver all restore an entry without passing its channel on:
createExtensionInstallDepsbuilds the install context without achannel(src/cli/create_extension_install_deps.ts:92-103, wherecreateInstallContext(_name, _version)ignores its arguments).extensionInstallpasses only the name and version (src/libswamp/extensions/install.ts:241-248).- The auto-resolver's install context has no
channeleither (src/cli/auto_resolver_adapters.ts:289-302).
installExtension then calls downloadArchive and getChecksum with no channel. It also rewrites the entry with channel: ctx.channel, which is undefined (src/libswamp/extensions/pull.ts:1363-1375). So restoring a beta or rc entry queries the registry without its channel, and the lockfile loses the channel. Later extension update and list then treat it as a stable-channel entry.
2. A parent's expectedChecksum leaks into its dependencies
installExtension recurses into dependencies with { ...ctx, depth: ctx.depth + 1, channel: depChannel } (src/libswamp/extensions/pull.ts:1421-1425). The spread copies the parent's expectedChecksum. That field is set by extensionInstall (install.ts:247-249) and by the auto-resolver (auto_resolver_adapters.ts:299-301).
A dependency that is missing from the lockfile is then checked against the parent's archive checksum. The check fails with an integrity error (pull.ts:899-909), after the parent's lockfile entry was already written. The command reports a failure while leaving the parent installed and the dependency missing.
Proposed fix
ExtensionInstallDeps.createInstallContexttakes the entry's channel, and the CLI factory sets it.extensionInstallpassesentry.channel. Check the serve handler (src/serve/handlers/admin_handlers.ts:705) and repo upgrade (src/cli/commands/repo_init.ts:209), which share the factory.- The auto-resolver passes the pinned entry's channel.
- The dependency recursion clears
expectedChecksumfor the child. The child's channel stays the one resolved for the dependency, as today. - While in that code:
InstallExtensionService's collision rollback rewrites the prior entry withoutchannelandpulledAt(src/libswamp/extensions/install_extension_service.ts:406-415). Carry both, so a rollback doesn't drop the channel either.
Testing
- Unit tests in
install_test.tsandcreate_extension_install_deps_test.ts: restoring a beta-channel entry passes the channel todownloadArchiveandgetChecksum, and the rewritten entry keeps it. - Unit test in
pull_test.ts: a parent restored withexpectedChecksuminstalls a dependency missing from the lockfile without an integrity error, and the dependency's own download is not checked against the parent's checksum. - Unit test in
auto_resolver_adapters_test.ts: a pinned beta entry is re-installed with its channel. - Unit test in
install_extension_service_test.ts: a collision rollback keepschannelandpulledAt.
Compatibility
No output, flag or lockfile-format changes. Existing entries without a channel behave as before.
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.