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.

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.

Appendix: 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)