{"record":{"id":"e224994ca0e54603","repo":"router-for-me/CLIProxyAPI","slug":"zip-contains-multiple-target-dynamic-libraries","errorCode":null,"errorMessage":"zip contains multiple target dynamic libraries","messagePattern":"zip contains multiple target dynamic libraries","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/install.go","lineNumber":343,"sourceCode":"\t\t\treturn nil, 0, errClean\n\t\t}\n\t\tif file.FileInfo().IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\tif !regularZipFile(file) {\n\t\t\treturn nil, 0, fmt.Errorf(\"zip entry %s is not a regular file\", file.Name)\n\t\t}\n\t\tif !hasDynamicLibraryExtension(cleanedName) {\n\t\t\tcontinue\n\t\t}\n\t\tif cleanedName != targetName && cleanedName != versionedTargetName {\n\t\t\tif path.Base(cleanedName) == targetName || path.Base(cleanedName) == versionedTargetName {\n\t\t\t\treturn nil, 0, fmt.Errorf(\"target dynamic library must be at zip root\")\n\t\t\t}\n\t\t\treturn nil, 0, fmt.Errorf(\"dynamic library filename must be %s or %s\", targetName, versionedTargetName)\n\t\t}\n\t\tif target != nil {\n\t\t\treturn nil, 0, fmt.Errorf(\"zip contains multiple target dynamic libraries\")\n\t\t}\n\t\ttarget = file\n\t}\n\tif target == nil {\n\t\treturn nil, 0, fmt.Errorf(\"zip does not contain %s\", targetName)\n\t}\n\n\thandle, errOpen := target.Open()\n\tif errOpen != nil {\n\t\treturn nil, 0, fmt.Errorf(\"open %s: %w\", targetName, errOpen)\n\t}\n\tdefer func() {\n\t\tif errClose := handle.Close(); errClose != nil {\n\t\t\tlog.WithError(errClose).Debug(\"failed to close plugin archive entry\")\n\t\t}\n\t}()\n\tdata, errRead := io.ReadAll(handle)\n\tif errRead != nil {","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/install.go#L325-L361","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Rebuild the release archive so it contains exactly one library entry at the zip root, named either <id><ext> or <id>-v<version><ext>","Delete the duplicate entry from the zip (unzip, remove the extra library, re-zip) and retry the install","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"],"exampleFix":"# before: zip contains\n#   myplugin.so\n#   myplugin-v1.2.3.so\nzip -d plugin.zip 'myplugin-v1.2.3.so'\n# after: zip contains only\n#   myplugin.so","handlingStrategy":"validation","validationCode":"// before InstallArchive, ensure exactly one root-level target library\nfunc countTargetLibs(archiveData []byte, id, version, goos string) (int, error) {\n    r, err := zip.NewReader(bytes.NewReader(archiveData), int64(len(archiveData)))\n    if err != nil { return 0, err }\n    target := id + pluginExtension(goos)\n    versioned := id + \"-v\" + version + pluginExtension(goos)\n    n := 0\n    for _, f := range r.File {\n        name := path.Clean(f.Name)\n        if name == target || name == versioned { n++ }\n    }\n    return n, nil\n}\n// n != 1 -> reject the archive before calling InstallArchive","typeGuard":null,"tryCatchPattern":"if _, err := store.InstallArchive(data, plugin, opts); err != nil {\n    if strings.Contains(err.Error(), \"multiple target dynamic libraries\") {\n        // packaging defect: fail the release, do not retry\n    }\n}","preventionTips":["Generate release zips from a single build step that writes exactly one library named <id>-v<version><ext> at the root","Add an archive-lint step to CI: unzip -l and assert one .so/.dylib/.dll entry","Never append to an existing zip during releases"],"tags":["pluginstore","zip","packaging","install"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}