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

Relationships

#3246 Extension auto-resolution installs an unrelated extension when a registry search guess does not provide the type

Opened by stack72 · 10/11/2026· Shipped 10/11/2026

What happened

Auto-resolution of a type that no extension publishes installs an unrelated extension picked by a fuzzy registry search, writes it into the tracked lockfile, and reports success:

$ swamp model create @swamp/definitely-not-a-real-type-3243 probe
  Resolving   @swamp/definitely-not-a-real-type-3243 not found locally, searching registry...
 Installing   @swamp/gcp/realtimebidding@2026.09.29.1 (Google Cloud realtimebidding infrastructure models)
  Installed   @swamp/gcp/realtimebidding@2026.09.29.1 (8 models registered)

Error: Unknown model type: @swamp/definitely-not-a-real-type-3243. Available types: command/shell, @swamp/gcp/realtimebidding/bidders, ...

After this, .swamp/pulled-extensions/@swamp/gcp/realtimebidding/ exists and extensions/models/upstream_extensions.json carries an entry for it, so the unrelated extension lands in the user's next commit. A second run reports @swamp/gcp/realtimebidding@2026.09.29.1 is installed but does not provide @swamp/definitely-not-a-real-type-3243, which is accurate, but the extension stays installed.

Realistic typos in the default-trusted @swamp collective hit the same path. The search returns:

Type typed Extension that would be installed
@swamp/aws/ecc2/instance @swamp/aws/workspacesinstances
@swamp/gcp/computee/instance @swamp/aws/workspacesinstances (AWS for a GCP type)

Reproduced with the released binary (20261011.001259.0) in a fresh swamp repo init --tool none repo against swamp-club.com.

Cause

  1. The search fallback takes the first hit unverified. findExtensionForType (src/domain/extensions/extension_auto_resolver.ts ~609) tries direct name candidates (the type and its parent prefixes), then calls searchExtensions({ q, collective, perPage: 1 }) with the type's segments as words and returns the first result. Nothing checks that the hit provides the type. The made-up type above matched on the word "real".
  2. A fresh install never checks the type was provided. installAndLoadLocked (~495) prints installed and returns true after extensionInstaller.install(). The already-installed (intact) branch does check extensionInstaller.providesType(normalizedType) and reports installedWithoutType / alreadyInstalledButFailed; the fresh-install branch does not.

findExtensionForType is shared, so the unverified guess also reaches describeTypeInRegistry (harmless: config_fields.ts matches the exact type in the returned metadata) and datastoreExtensionCandidates (the guessed extension joins the set exempted to write the in-repo lockfile during datastore repair).

The search fallback is load-bearing

A scan of all 1,709 registry extensions (4,611 published types) found 200 types reachable only through search, because the extension name is not a prefix of the type, e.g. @shelson/newrelic publishes @shelson/newrelic-account, @dougschaefer/pexip-infinity publishes @dougschaefer/pexip-conference. None are in @swamp; all are community collectives. So the fallback cannot simply be removed.

For those 200, the first search hit is wrong 4 times today:

Type Provided by Search's first hit (installed today)
@dougschaefer/azure-firewall @dougschaefer/azure @dougschaefer/opnsense-firewall
@keeb/tmdb-lookup @keeb/mms @keeb/twilio/lookups
@rjeschmi/rancher-openstack @rjeschmi/vm @rjeschmi/kolla-openstack
@rjeschmi/vm-provider-openstack @rjeschmi/vm @rjeschmi/kolla-openstack

Expected

An extension is installed by auto-resolution only when it is known to provide the requested type. Otherwise the user sees no extension publishes this type, and nothing is installed or written to the lockfile.

Proposed fix

  1. Verify search hits against registry metadata in findExtensionForType. Each version's published detail (GET /api/v1/extensions/<name>/latest, latestVersionDetail.models|vaults|datastores|drivers[].type, already exposed as ExtensionLookupPort.getLatestVersionDetail) lists every type it provides. Request several search results instead of one and return the first whose metadata lists the type (case-insensitive, as config_fields.ts does). This fixes made-up types, typos and the 4 wrong picks above, and the shared callers inherit it. If the lookup port cannot fetch detail, keep today's behaviour only behind the post-install check below.
  2. Post-install backstop. In the fresh-install branch of installAndLoadLocked, run the same providesType check as the intact branch and report installedWithoutType instead of installed when the type is absent.
  3. No verified match: report notFound, optionally with a hint naming the closest search hit and swamp extension pull <name>, without installing it.

No interactive prompt: a verified match is the right extension, and an unverified guess is better offered as a hint than a prompt that cannot run in CI, --json, serve or workers.

Not a regression: the unverified search fallback has existed since auto-resolution shipped (#725).

Tests

  • Unit: findExtensionForType skips a search hit whose metadata lacks the type and returns a later hit that lists it; returns null when none do; direct candidates unchanged.
  • Unit: fresh install of an extension without the type reports installedWithoutType, not installed.
  • Integration: resolving a made-up type against a lookup whose search returns an unrelated extension installs nothing and writes no lockfile entry.
02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 2 MOREREVIEW+ 9 MOREPR_LINKED+ 2 MORESESSION_SUMMARIZED

Shipped

10/11/2026, 5:42:41 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack7210/11/2026, 3:37:05 PM

Sign in to post a ripple.