{"record":{"id":"4cef098a8a402286","repo":"router-for-me/CLIProxyAPI","slug":"release-tag-is-required","errorCode":null,"errorMessage":"release tag is required","messagePattern":"release tag is required","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/pluginstore/github.go","lineNumber":91,"sourceCode":"\tif errDownload != nil {\n\t\treturn Release{}, errDownload\n\t}\n\tvar release Release\n\tif errDecode := json.Unmarshal(data, &release); errDecode != nil {\n\t\treturn Release{}, fmt.Errorf(\"decode release: %w\", errDecode)\n\t}\n\treturn release, nil\n}\n\n// FetchReleaseByTag returns a published release by its exact GitHub tag.\nfunc (c Client) FetchReleaseByTag(ctx context.Context, plugin Plugin, tag string) (Release, error) {\n\towner, repo, errRepository := GitHubRepositoryParts(plugin.Repository)\n\tif errRepository != nil {\n\t\treturn Release{}, errRepository\n\t}\n\ttag = strings.TrimSpace(tag)\n\tif tag == \"\" {\n\t\treturn Release{}, fmt.Errorf(\"release tag is required\")\n\t}\n\treleaseURL := fmt.Sprintf(\n\t\t\"https://api.github.com/repos/%s/%s/releases/tags/%s\",\n\t\turl.PathEscape(owner),\n\t\turl.PathEscape(repo),\n\t\turl.PathEscape(tag),\n\t)\n\tdata, errDownload := c.get(ctx, releaseURL, \"application/vnd.github+json\", RequestKindMetadata, 0)\n\tif errDownload != nil {\n\t\treturn Release{}, errDownload\n\t}\n\tvar release Release\n\tif errDecode := json.Unmarshal(data, &release); errDecode != nil {\n\t\treturn Release{}, fmt.Errorf(\"decode release: %w\", errDecode)\n\t}\n\treturn release, nil\n}\n","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/github.go#L73-L109","documentation":"FetchReleaseByTag resolves a specific published release via /releases/tags/{tag}. After trimming, an empty tag string is rejected with 'release tag is required' before any network call is made. This is pure input validation on the tag parameter, independent of the plugin repository.","triggerScenarios":"Calling FetchReleaseByTag(ctx, plugin, tag) where tag is \"\", whitespace-only, or a value that normalizes to empty (e.g. a missing manifest field passed through).","commonSituations":"Plugin manifest has an empty version/tag field; caller derives the tag from user input or a template and forgets a default; trailing/leading spaces are trimmed so only genuinely empty inputs trigger it.","solutions":["Check the source of the tag — usually the plugin manifest's version or pin field is empty; populate it.","Default to the latest release path (FetchLatestRelease) when no explicit tag is configured.","Validate the tag is non-empty right after parsing your config and fail with a clearer configuration error."],"exampleFix":"// before\nrelease, err := client.FetchReleaseByTag(ctx, plugin, plugin.Version) // \"\" -> 'release tag is required'\n\n// after\ntag := strings.TrimSpace(plugin.Version)\nif tag == \"\" {\n    return client.FetchLatestRelease(ctx, plugin)\n}\nrelease, err := client.FetchReleaseByTag(ctx, plugin, tag)","handlingStrategy":"validation","validationCode":"tag := strings.TrimSpace(cfgPin)\nif tag == \"\" {\n    return fmt.Errorf(\"plugin %s: version pin missing in config\", plugin.Name)\n}","typeGuard":"func hasReleaseTag(tag string) bool { return strings.TrimSpace(tag) != \"\" }","tryCatchPattern":null,"preventionTips":["Require a version pin in config for by-tag installs or fall back to FetchLatestRelease.","Fail config validation early instead of at fetch time."],"tags":["validation","github-api","release-tag","input-validation"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}