router-for-me/CLIProxyAPI · error

dynamic library filename must be %s or %s

Error message

dynamic library filename must be %s or %s

What it means

Thrown by readTargetLibrary when an archive entry has a dynamic-library extension but its name matches neither the plain target name (trimmed id + platform extension: .dylib/.dll/.so) nor the versioned name (id + "-v" + normalized version + extension), and its basename does not match either — i.e. the library is present but named differently than the plugin ID/version demand. The message includes both accepted names.

Source

Thrown at internal/pluginstore/install.go:340

	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 {
			log.WithError(errClose).Debug("failed to close plugin archive entry")
		}

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Rename the library inside the archive to exactly <id><ext> or <id>-v<version><ext> for the target platform
  2. Align the plugin ID in your metadata with the artifact filename actually built
  3. Double-check options.GOOS (and the resulting extension) matches the archive contents

Example fix

# before (plugin.ID = "myplugin", version "1.0.0", linux)
zip plugin.zip libmyplugin_linux.so # rejected

# after
mv libmyplugin_linux.so myplugin.so
zip plugin.zip myplugin.so # or name it myplugin-v1.0.0.so
Defensive patterns

Strategy: validation

Validate before calling

func expectedLibraryNames(id, version, goos string) (string, string) {
    ext := ".so"
    switch strings.ToLower(strings.TrimSpace(goos)) {
    case "darwin", "mac", "macos", "osx":
        ext = ".dylib"
    case "windows":
        ext = ".dll"
    }
    v := strings.TrimPrefix(strings.TrimPrefix(strings.TrimSpace(version), "v"), "V")
    return strings.TrimSpace(id) + ext, strings.TrimSpace(id) + "-v" + v + ext
}

func archiveUsesExpectedNames(data []byte, id, version, goos string) error {
    zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
    if err != nil {
        return err
    }
    plain, versioned := expectedLibraryNames(id, version, goos)
    found := false
    for _, f := range zr.File {
        if !f.FileInfo().IsDir() && (f.Name == plain || f.Name == versioned) {
            found = true
        }
    }
    if !found {
        return fmt.Errorf("archive must contain %s or %s at the root", plain, versioned)
    }
    return nil
}

Type guard

func isLibraryNameError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "dynamic library filename must be")
}

Try / catch

if _, err := pluginstore.InstallArchive(data, plugin, options); err != nil {
    if isLibraryNameError(err) {
        // rename the library to <id><ext> or <id>-v<version><ext> and rebuild the archive
    }
}

Prevention

When it happens

Trigger: Archive contains 'libmyplugin.so' or 'myplugin_linux_amd64.so' when the plugin ID is 'myplugin' and version '1.0.0' — expected exactly 'myplugin.so' or 'myplugin-v1.0.0.so'. Also mismatched platform extensions (a .so inside while running on darwin where .dylib is expected).

Common situations: Build system names the artifact with its own convention (lib prefix, platform suffix) that differs from the registry plugin ID; plugin ID changed without rebuilding artifacts; cross-platform archives where each platform names libraries differently; wrong GOOS passed in InstallOptions so the expected extension differs.

Related errors


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