router-for-me/CLIProxyAPI · error
plugin_install_failed
plugin_install_failed
Error message
%s: %w
What it means
Wrapped error produced inside installPluginStoreGitHubRelease (plugin_store.go:400) with code plugin_install_failed: each failed attempt to install a GitHub-release plugin from one tag candidate is wrapped as "<tag>: <cause>" and collected. These per-tag errors are then joined into the final 'install release by tag' error, so this message appears as a component line inside errors.Join output.
Source
Thrown at internal/api/handlers/management/plugin_store.go:400
}
return pluginstore.ManifestFromPlugin(source, plugin)
}
return pluginstore.Manifest{}, fmt.Errorf("direct plugin version %q not found", version)
}
func installPluginStoreGitHubRelease(ctx context.Context, client pluginstore.Client, plugin pluginstore.Plugin, requestedVersion string, options pluginstore.InstallOptions) (pluginstore.InstallResult, error) {
version := normalizePluginStoreRequestedVersion(requestedVersion)
if version == "" {
return client.Install(ctx, plugin, options)
}
tags := pluginStoreReleaseTagCandidates(requestedVersion)
errs := make([]error, 0, len(tags))
for _, tag := range tags {
result, errInstall := client.InstallVersion(ctx, plugin, tag, version, options)
if errInstall == nil {
return result, nil
}
errs = append(errs, fmt.Errorf("%s: %w", tag, errInstall))
}
return pluginstore.InstallResult{}, fmt.Errorf("install release by tag: %w", errors.Join(errs...))
}
func pluginStoreManifestForInstall(source pluginstore.Source, plugin pluginstore.Plugin, result pluginstore.InstallResult) (pluginstore.Manifest, error) {
installType := strings.TrimSpace(result.InstallType)
if installType == "" {
installType = pluginstore.PluginInstallType(plugin)
}
switch installType {
case pluginstore.InstallTypeDirect:
plugin.Version = strings.TrimSpace(result.Version)
plugin.Install = pluginstore.NormalizeInstallPlan(plugin.Install)
return pluginstore.ManifestFromPlugin(source, plugin)
case pluginstore.InstallTypeGitHubRelease:
releaseTag := strings.TrimSpace(result.ReleaseTag)
if releaseTag == "" {
return pluginstore.Manifest{}, fmt.Errorf("release tag is required")View on GitHub (pinned to 78f0c4079e)
Solutions
- Read the joined error: it contains one 'tag: cause' line per candidate; address the underlying cause (404, rate limit, TLS, auth)
- Confirm the release actually exists and note its exact tag: curl https://api.github.com/repos/<owner>/<repo>/releases
- For rate limits, wait or configure GitHub credentials used by the store client
- If the release uses a nonstandard tag scheme, request the full tag exactly as published
Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight: confirm at least one tag candidate exists before installing.
tags := []string{version, "v" + version}
for _, tag := range tags {
resp, err := http.Head(fmt.Sprintf("https://github.com/%s/releases/tag/%s", repo, tag))
if err == nil && resp.StatusCode == http.StatusOK {
return // a candidate exists
}
}
return fmt.Errorf("no release tag found for %s", version) Try / catch
var err error
for attempt := 0; attempt < 3; attempt++ {
_, err = installPluginStoreGitHubRelease(ctx, client, plugin, version, opts)
if err == nil {
break
}
if isTransient(err) { // rate limit, network: inspect joined causes
time.Sleep(time.Duration(attempt+1) * 2 * time.Second)
continue
}
break
} Prevention
- Read the per-tag causes in the joined error instead of retrying blindly
- Verify releases exist (and have assets) before scripting pinned installs
- Configure GitHub credentials and ensure egress to github.com for the server host
When it happens
Trigger: One of the two tag candidates (the raw version and its 'v'-prefixed twin from pluginStoreReleaseTagCandidates) failing in client.InstallVersion: release tag not found, asset download blocked, extraction failure, checksum mismatch. You normally see this string as part of the joined error, not alone.
Common situations: GitHub API rate limiting or network egress restrictions from the server; releases tagged without the 'v' convention so one candidate always 404s; private repos without a token.
Related errors
- plugin_manifest_failed
- plugin_update_requires_restart
- plugin_manifest_invalid
- invalid_request
- download artifact: %w
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/739f6fd3b71052ba.
Report an issue: GitHub.