router-for-me/CLIProxyAPI · error

zip contains multiple target dynamic libraries

Error message

zip contains multiple target dynamic libraries

What it means

Thrown by readTargetLibrary in internal/pluginstore/install.go when a plugin release archive contains more than one entry whose cleaned name equals the expected root-level library name (e.g. myplugin.so or myplugin-v1.2.3.so). The installer needs exactly one target library per archive so it can deterministically pick the artifact to install; a second match makes the choice ambiguous, so the install aborts before anything is written.

Source

Thrown at internal/pluginstore/install.go:343

			return nil, 0, errClean
		}
		if file.FileInfo().IsDir() {
			continue
		}
		if !regularZipFile(file) {
			return nil, 0, fmt.Errorf("zip entry %s is not a regular file", file.Name)
		}
		if !hasDynamicLibraryExtension(cleanedName) {
			continue
		}
		if cleanedName != targetName && cleanedName != versionedTargetName {
			if path.Base(cleanedName) == targetName || path.Base(cleanedName) == versionedTargetName {
				return nil, 0, fmt.Errorf("target dynamic library must be at zip root")
			}
			return nil, 0, fmt.Errorf("dynamic library filename must be %s or %s", targetName, versionedTargetName)
		}
		if target != nil {
			return nil, 0, fmt.Errorf("zip contains multiple target dynamic libraries")
		}
		target = file
	}
	if target == nil {
		return nil, 0, fmt.Errorf("zip does not contain %s", targetName)
	}

	handle, errOpen := target.Open()
	if errOpen != nil {
		return nil, 0, fmt.Errorf("open %s: %w", targetName, errOpen)
	}
	defer func() {
		if errClose := handle.Close(); errClose != nil {
			log.WithError(errClose).Debug("failed to close plugin archive entry")
		}
	}()
	data, errRead := io.ReadAll(handle)
	if errRead != nil {

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Rebuild the release archive so it contains exactly one library entry at the zip root, named either <id><ext> or <id>-v<version><ext>
  2. Delete the duplicate entry from the zip (unzip, remove the extra library, re-zip) and retry the install
  3. If you do not control the archive, fetch a release whose asset zip is produced by the standard plugin release template instead of a manual zip

Example fix

# before: zip contains
#   myplugin.so
#   myplugin-v1.2.3.so
zip -d plugin.zip 'myplugin-v1.2.3.so'
# after: zip contains only
#   myplugin.so
Defensive patterns

Strategy: validation

Validate before calling

// before InstallArchive, ensure exactly one root-level target library
func countTargetLibs(archiveData []byte, id, version, goos string) (int, error) {
    r, err := zip.NewReader(bytes.NewReader(archiveData), int64(len(archiveData)))
    if err != nil { return 0, err }
    target := id + pluginExtension(goos)
    versioned := id + "-v" + version + pluginExtension(goos)
    n := 0
    for _, f := range r.File {
        name := path.Clean(f.Name)
        if name == target || name == versioned { n++ }
    }
    return n, nil
}
// n != 1 -> reject the archive before calling InstallArchive

Try / catch

if _, err := store.InstallArchive(data, plugin, opts); err != nil {
    if strings.Contains(err.Error(), "multiple target dynamic libraries") {
        // packaging defect: fail the release, do not retry
    }
}

Prevention

When it happens

Trigger: Calling Client.Install, Client.InstallVersion, Client.InstallDirect, or InstallArchive with an archive zip that lists both 'myplugin.so' and 'myplugin-v1.2.3.so' at the root, or the same filename in two directories that both clean to the root name (e.g. './myplugin.so' alongside 'myplugin.so'). The loop at install.go:322-346 sets target on the first match and errors on the second.

Common situations: A release workflow that zips the whole build directory and accidentally includes both the unversioned build output and the version-renamed copy; CI jobs that append previously-built artifacts to an existing zip; a repackaged third-party zip that duplicated the binary in a subfolder that path.Clean collapses to the root name.

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/e224994ca0e54603. Report an issue: GitHub.