router-for-me/CLIProxyAPI · error

release-tag %q resolves version %q, want %q

Error message

release-tag %q resolves version %q, want %q

What it means

For github-release installs, Validate() derives a version from the tag via ReleaseVersion() and compares it to the manifest's normalized version field. If they differ, the manifest is internally inconsistent: the pinned tag would install a different version than the manifest declares, so validation fails.

Source

Thrown at internal/pluginstore/manifest.go:142

			return validatePinnedArtifactURLs(plan.Artifacts)
		}
		return validateManifestSourceURL(m.SourceURL)
	case InstallTypeGitHubRelease:
		releaseTag := strings.TrimSpace(m.ReleaseTag)
		if releaseTag == "" {
			return fmt.Errorf("missing required field release-tag")
		}
		plugin := m.Plugin()
		plugin.Install = InstallPlan{Type: InstallTypeGitHubRelease}
		if errValidate := ValidatePlugin(plugin); errValidate != nil {
			return errValidate
		}
		releaseVersion, errVersion := ReleaseVersion(Release{TagName: releaseTag})
		if errVersion != nil {
			return errVersion
		}
		if releaseVersion != normalizeVersion(version) {
			return fmt.Errorf("release-tag %q resolves version %q, want %q", releaseTag, releaseVersion, normalizeVersion(version))
		}
		return nil
	default:
		return fmt.Errorf("unsupported install type %q", m.Install.Type)
	}
}

func validatePinnedArtifactURLs(artifacts []Artifact) error {
	for index, artifact := range artifacts {
		parsed, errParse := url.Parse(strings.TrimSpace(artifact.URL))
		if errParse != nil {
			return fmt.Errorf("artifacts[%d]: invalid artifact url", index)
		}
		if parsed.User != nil {
			return fmt.Errorf("artifacts[%d]: pinned artifact url must not contain credentials", index)
		}
		if parsed.RawQuery != "" || parsed.Fragment != "" {
			return fmt.Errorf("artifacts[%d]: pinned artifact url must not contain query or fragment", index)

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Make release-tag and version describe the same release, e.g. version: 1.3.0 with release-tag: v1.3.0
  2. If your tags carry no v-prefix, use release-tag: 1.3.0 — what matters is what ReleaseVersion extracts equals normalizeVersion(version)
  3. Wire CI to render both fields from the same source (git describe / release variable) so they cannot drift

Example fix

# before
version: 1.2.0
release-tag: v1.3.0
# release-tag "v1.3.0" resolves version "1.3.0", want "1.2.0"

# after
version: 1.3.0
release-tag: v1.3.0
Defensive patterns

Strategy: validation

Validate before calling

rv, err := pluginstore.ReleaseVersion(pluginstore.Release{TagName: m.ReleaseTag})
if err != nil { return err }
norm := m.Version
if len(norm) > 1 && (norm[0] == 'v' || norm[0] == 'V') { norm = norm[1:] }
if rv != norm { return fmt.Errorf("tag %s does not match version %s", m.ReleaseTag, m.Version) }

Type guard

func tagMatchesVersion(tag, version string) bool { rv, err := pluginstore.ReleaseVersion(pluginstore.Release{TagName: tag}); return err == nil && rv == strings.TrimPrefix(strings.TrimPrefix(version, "v"), "V") }

Try / catch

if err := m.Validate(); err != nil && strings.Contains(err.Error(), "resolves version") { /* re-derive version from the tag and rewrite manifest */ }

Prevention

When it happens

Trigger: Manifest with version: 1.2.0 but release-tag: v1.3.0; or tag "release-2024.01" whose extracted version does not equal the version field; or version "1.2" vs tag "v1.2.0" when ReleaseVersion yields "1.2.0".

Common situations: Bumping version in one place but not the other during release automation; renaming tags (v-prefix added/removed) without updating the manifest; cherry-picking a manifest template and only editing one field.

Related errors


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