router-for-me/CLIProxyAPI · error

direct install plugin %q version %q: %w

Error message

direct install plugin %q version %q: %w

What it means

Wrapped error from directPluginVersion when the resolved plugin's TOP-LEVEL version exactly matches the requested version (normalizeVersion(plugin.Version) == version), so its Install plan is used directly, but ValidateInstallPlan rejects that plan. The id and requested version are included; the underlying validation failure (e.g. invalid/missing artifact entries) is chained via %w.

Source

Thrown at internal/pluginstore/install.go:219

	resolved, okPlugin := registry.PluginByID(manifest.ID)
	if !okPlugin {
		return Plugin{}, fmt.Errorf("direct install plugin %q not found in source", strings.TrimSpace(manifest.ID))
	}
	if PluginInstallType(resolved) != InstallTypeDirect {
		return Plugin{}, fmt.Errorf("direct install plugin %q resolved as %q", strings.TrimSpace(manifest.ID), PluginInstallType(resolved))
	}
	return directPluginVersion(resolved, manifest.ID, manifest.Version)
}

func directPluginVersion(plugin Plugin, id string, version string) (Plugin, error) {
	id = strings.TrimSpace(id)
	version = normalizeVersion(version)
	if normalizeVersion(plugin.Version) == version {
		plugin.Version = version
		plugin.Install = NormalizeInstallPlan(plugin.Install)
		plugin.Install.Type = InstallTypeDirect
		if errPlan := ValidateInstallPlan(plugin.Install); errPlan != nil {
			return Plugin{}, fmt.Errorf("direct install plugin %q version %q: %w", id, version, errPlan)
		}
		return plugin, nil
	}
	for _, candidate := range plugin.Versions {
		if normalizeVersion(candidate.Version) != version {
			continue
		}
		plugin.Version = version
		plugin.Install = NormalizeInstallPlan(candidate.Install)
		if plugin.Install.Type == "" {
			plugin.Install.Type = InstallTypeDirect
		}
		if plugin.Install.Type != InstallTypeDirect {
			return Plugin{}, fmt.Errorf("direct install plugin %q version %q resolved as %q", id, version, plugin.Install.Type)
		}
		if errPlan := ValidateInstallPlan(plugin.Install); errPlan != nil {
			return Plugin{}, fmt.Errorf("direct install plugin %q version %q: %w", id, version, errPlan)
		}

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Unwrap the error and read the ValidateInstallPlan message to see which plan field is invalid
  2. Fix the registry entry for that plugin/version: ensure the install plan has at least one well-formed artifact (URL + checksum) for the target platform
  3. If the registry is remote, re-fetch it to rule out a stale cached copy

Example fix

// before: registry entry
// { "id": "p", "version": "1.0.0", "install": { "type": "direct", "artifacts": [] } }
plugin, err := client.ResolveDirect(ctx, manifest) // "direct install plugin \"p\" version \"1.0.0\": ..."

// after
// { "id": "p", "version": "1.0.0", "install": { "type": "direct", "artifacts": [
//     { "url": "https://cdn.example.com/p-1.0.0-linux-amd64.zip", "checksum": "sha256:...", "os": "linux", "arch": "amd64" } ] } }
Defensive patterns

Strategy: try-catch

Try / catch

plugin, err := client.ResolveDirect(ctx, manifest)
if err != nil {
    if strings.Contains(err.Error(), fmt.Sprintf("direct install plugin %q version %q:", manifest.ID, manifest.Version)) {
        cause := errors.Unwrap(err) // ValidateInstallPlan detail
        return fmt.Errorf("registry install plan invalid for %s@%s: %w", manifest.ID, manifest.Version, cause)
    }
    return err
}

Prevention

When it happens

Trigger: Registry entry whose top-level version matches the requested one but whose install plan is invalid: zero usable artifacts, malformed artifact URLs, missing checksums, or an invalid plan type after NormalizeInstallPlan + forced InstallTypeDirect.

Common situations: Registry published with an incomplete artifacts list for the current version; artifact entries filtered out because they lack URLs; registry hand-edited and plan fields left blank; schema change in the registry format leaving plan fields unparsed.

Related errors


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