{"record":{"id":"2aa0c96e065232ed","repo":"router-for-me/CLIProxyAPI","slug":"target-dynamic-library-must-be-at-zip-root","errorCode":null,"errorMessage":"target dynamic library must be at zip root","messagePattern":"target dynamic library must be at zip root","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/install.go","lineNumber":338,"sourceCode":"\tversionedTargetName := versionedPluginFileName(id, version, goos)\n\tvar target *zip.File\n\tfor _, file := range reader.File {\n\t\tcleanedName, errClean := cleanZipName(file.Name)\n\t\tif errClean != nil {\n\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 {","sourceCodeStart":320,"sourceCodeEnd":356,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/install.go#L320-L356","documentation":"Thrown by readTargetLibrary when a dynamic-library entry's base name matches the expected target (id+ext or id-v+version+ext) but the entry is nested inside a subdirectory rather than at the zip root (cleanedName != base). The installer locates the library at the archive root so the extracted path is deterministic and cannot smuggle path components; a nested placement with the right basename is treated as a layout error.","triggerScenarios":"Archive laid out as 'bin/myplugin.so' or 'dist/linux/amd64/myplugin-v1.0.0.so' — the basename matches but the full cleaned path is not the root. Any matching-extension entry in a subdirectory with the target basename triggers this.","commonSituations":"Build pipelines that zip a project directory (creating 'target/release/' or 'build/' prefixes); CI artifacts wrapping binaries in a top-level folder; repackaging a release tarball into a zip without flattening.","solutions":["Rebuild the zip so the library sits at the root: cd into the directory containing the .so/.dylib/.dll and zip from there","Adjust the packaging script to flatten output before archiving (e.g. '(cd build/out && zip ../plugin.zip myplugin.so)')"],"exampleFix":"# before\nzip plugin.zip build/linux/myplugin.so # entry: build/linux/myplugin.so\n\n# after\ncd build/linux && zip ../../plugin.zip myplugin.so # entry: myplugin.so (root)","handlingStrategy":"validation","validationCode":"func libraryAtZipRoot(data []byte, id, version, goos string) error {\n    zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))\n    if err != nil {\n        return err\n    }\n    ext := \".so\"\n    switch goos {\n    case \"darwin\":\n        ext = \".dylib\"\n    case \"windows\":\n        ext = \".dll\"\n    }\n    plain := strings.TrimSpace(id) + ext\n    versioned := strings.TrimSpace(id) + \"-v\" + strings.TrimPrefix(strings.TrimSpace(version), \"v\") + ext\n    for _, f := range zr.File {\n        if f.FileInfo().IsDir() || !strings.HasSuffix(f.Name, ext) {\n            continue\n        }\n        if f.Name != path.Base(f.Name) && (path.Base(f.Name) == plain || path.Base(f.Name) == versioned) {\n            return fmt.Errorf(\"library %s is nested at %s; must be at zip root\", path.Base(f.Name), f.Name)\n        }\n    }\n    return nil\n}","typeGuard":"func isNestedLibraryError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"must be at zip root\")\n}","tryCatchPattern":"if _, err := pluginstore.InstallArchive(data, plugin, options); err != nil {\n    if isNestedLibraryError(err) {\n        // repackage: flatten the archive so the library sits at the root\n    }\n}","preventionTips":["Zip from inside the output directory, not from the project root","Add a packaging test that asserts the library entry name equals <id><ext> or <id>-v<version><ext> at root","Standardize one packaging script per plugin so layout cannot drift"],"tags":["go","plugin-store","zip","packaging","layout"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}