router-for-me/CLIProxyAPI · error
target dynamic library must be at zip root
Error message
target dynamic library must be at zip root
What it means
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.
Source
Thrown at internal/pluginstore/install.go:338
versionedTargetName := versionedPluginFileName(id, version, goos)
var target *zip.File
for _, file := range reader.File {
cleanedName, errClean := cleanZipName(file.Name)
if errClean != nil {
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 {View on GitHub (pinned to 78f0c4079e)
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)')
Example fix
# before zip plugin.zip build/linux/myplugin.so # entry: build/linux/myplugin.so # after cd build/linux && zip ../../plugin.zip myplugin.so # entry: myplugin.so (root)
Defensive patterns
Strategy: validation
Validate before calling
func libraryAtZipRoot(data []byte, id, version, goos string) error {
zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
if err != nil {
return err
}
ext := ".so"
switch goos {
case "darwin":
ext = ".dylib"
case "windows":
ext = ".dll"
}
plain := strings.TrimSpace(id) + ext
versioned := strings.TrimSpace(id) + "-v" + strings.TrimPrefix(strings.TrimSpace(version), "v") + ext
for _, f := range zr.File {
if f.FileInfo().IsDir() || !strings.HasSuffix(f.Name, ext) {
continue
}
if f.Name != path.Base(f.Name) && (path.Base(f.Name) == plain || path.Base(f.Name) == versioned) {
return fmt.Errorf("library %s is nested at %s; must be at zip root", path.Base(f.Name), f.Name)
}
}
return nil
} Type guard
func isNestedLibraryError(err error) bool {
return err != nil && strings.Contains(err.Error(), "must be at zip root")
} Try / catch
if _, err := pluginstore.InstallArchive(data, plugin, options); err != nil {
if isNestedLibraryError(err) {
// repackage: flatten the archive so the library sits at the root
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- dynamic library filename must be %s or %s
- open zip: %w
- zip entry %s is not a regular file
- download artifact: %w
- direct install manifest missing source-url
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/2aa0c96e065232ed.
Report an issue: GitHub.