{"record":{"id":"1342157950492024","repo":"router-for-me/CLIProxyAPI","slug":"invalid-plugin-version-q-134215","errorCode":null,"errorMessage":"invalid plugin version %q","messagePattern":"invalid plugin version %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/manifest.go","lineNumber":108,"sourceCode":"\t\tInstall:     NormalizeInstallPlan(m.Install),\n\t}\n}\n\nfunc (m Manifest) InstallType() string {\n\tinstallType := strings.ToLower(strings.TrimSpace(m.Install.Type))\n\tif installType == \"\" {\n\t\treturn InstallTypeGitHubRelease\n\t}\n\treturn installType\n}\n\nfunc (m Manifest) Validate() error {\n\tversion := strings.TrimSpace(m.Version)\n\tif version == \"\" {\n\t\treturn fmt.Errorf(\"missing required field version\")\n\t}\n\tif !validPluginVersion(normalizeVersion(version)) {\n\t\treturn fmt.Errorf(\"invalid plugin version %q\", m.Version)\n\t}\n\tswitch m.InstallType() {\n\tcase InstallTypeDirect:\n\t\tif m.SchemaVersion != 0 && m.SchemaVersion != SchemaVersionV2 {\n\t\t\treturn fmt.Errorf(\"unsupported schema-version %d\", m.SchemaVersion)\n\t\t}\n\t\tif errID := validateManifestPluginID(m.ID); errID != nil {\n\t\t\treturn errID\n\t\t}\n\t\tplan := NormalizeInstallPlan(m.Install)\n\t\tplan.Type = InstallTypeDirect\n\t\tif len(plan.Artifacts) > 0 {\n\t\t\tif errValidate := ValidateInstallPlan(plan); errValidate != nil {\n\t\t\t\treturn errValidate\n\t\t\t}\n\t\t\treturn validatePinnedArtifactURLs(plan.Artifacts)\n\t\t}\n\t\treturn validateManifestSourceURL(m.SourceURL)","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/manifest.go#L90-L126","documentation":"Manifest.Validate() rejects the plugin manifest's version field. The version is trimmed and normalizeVersion() strips at most one leading 'v'/'V', then validPluginVersion() requires a non-empty string that does not still start with 'v' and matches ^[0-9][0-9A-Za-z.+-]*$. Anything else (letters first, spaces, 'vv' prefix, empty after trim is caught earlier) fails with this error.","triggerScenarios":"Calling pluginstore Manifest.Validate() (directly or via registry install/publish flows) with m.Version set to e.g. \"alpha-1\", \"v_v1\", \" 1.0 β\", \"vv1.2.3\", or any value whose first character after trimming one optional 'v' is not a digit.","commonSituations":"Hand-edited plugin manifest YAML/JSON using tags like \"release-1.0\" or \"latest\"; copying a Git branch name into version; double 'v' from templating (\"v\" + \"v1.0.0\"); non-ASCII or whitespace sneaking in from copy-paste.","solutions":["Set version to a plain dotted number, e.g. \"1.2.3\" (a single leading \"v1.2.3\" is also accepted because normalizeVersion strips it)","Remove spaces, unicode, and any second leading 'v' from the version string","If you need a tag-like name, remember only the version field is strict — put tags in release-tag for github-release installs and keep version numeric"],"exampleFix":"// before\nmanifest := pluginstore.Manifest{Version: \"release-1.0\"}\nerr := manifest.Validate() // invalid plugin version \"release-1.0\"\n\n// after\nmanifest := pluginstore.Manifest{Version: \"1.0.0\"}\nerr := manifest.Validate() // nil","handlingStrategy":"validation","validationCode":"var versionRe = regexp.MustCompile(`^[0-9][0-9A-Za-z.+-]*$`)\nfunc versionOK(v string) bool {\n    v = strings.TrimSpace(v)\n    if len(v) > 1 && (v[0] == 'v' || v[0] == 'V') { v = v[1:] }\n    return v != \"\" && !strings.HasPrefix(v, \"v\") && versionRe.MatchString(v)\n}\n// before submit:\nif !versionOK(m.Version) { return fmt.Errorf(\"fix version %q\", m.Version) }","typeGuard":"func validManifestVersion(v string) bool { return versionOK(v) }","tryCatchPattern":"err := m.Validate()\nif err != nil {\n    if strings.Contains(err.Error(), \"invalid plugin version\") { /* fix version field, re-validate */ }\n    return err\n}","preventionTips":["Render version from a single semver variable in release automation","Lint manifests in CI with a regex gate before publish","Never copy branch or tag names with letters-first prefixes into version"],"tags":["pluginstore","manifest","validation","version"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}