router-for-me/CLIProxyAPI · error

zip does not contain %s

Error message

zip does not contain %s

What it means

Thrown by readTargetLibrary in internal/pluginstore/install.go when the archive zip was parsed successfully but no entry matches the expected dynamic library filename. The expected name is derived from the plugin id, GOOS extension (install.go:319-320): <id>.so/.dylib/.dll or <id>-v<version><ext>. All entries lacking a dynamic library extension are skipped, so an archive of only READMEs or a misnamed binary yields this error.

Source

Thrown at internal/pluginstore/install.go:348

		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 {
		return nil, 0, fmt.Errorf("read %s: %w", targetName, errRead)
	}
	mode := target.FileInfo().Mode().Perm()
	if mode == 0 {
		mode = 0o755

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Verify the zip actually contains <id><ext> or <id>-v<version><ext> at the root: unzip -l artifact.zip
  2. Align options.GOOS with the platform the asset was built for (the asset selection uses the same GOOS/GOARCH)
  3. Fix the release packaging to rename the built library to the plugin id plus the correct extension before zipping
Defensive patterns

Strategy: validation

Validate before calling

func hasTargetLib(archiveData []byte, id, version, goos string) bool {
    r, err := zip.NewReader(bytes.NewReader(archiveData), int64(len(archiveData)))
    if err != nil { return false }
    ext := map[string]string{"darwin": ".dylib", "windows": ".dll"}[goos]
    if ext == "" { ext = ".so" }
    want := map[string]bool{id + ext: true, id + "-v" + version + ext: true}
    for _, f := range r.File {
        if want[path.Clean(f.Name)] { return true }
    }
    return false
}

Try / catch

if err := installAndLog(data, plugin, opts); err != nil {
    if strings.Contains(err.Error(), "zip does not contain") {
        log.Errorf("artifact for %s/%s lacks the expected library; check asset naming", opts.GOOS, opts.GOARCH)
    }
}

Prevention

When it happens

Trigger: InstallArchive or any Client.Install* path where the downloaded zip contains the library under a different name (e.g. 'libmyplugin.so' or 'plugin.bin'), targets a different OS extension than options.GOOS implies (a .dll in the zip while GOOS=linux expects .so), or the library sits in a subdirectory with a non-matching base name.

Common situations: Release assets built for one platform installed with another GOOS; plugin id in the registry not matching the binary name inside the artifact; an archive that ships source or docs instead of the compiled library because the release job skipped the build step.

Related errors


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