{"record":{"id":"3b09cd679b3648ba","repo":"router-for-me/CLIProxyAPI","slug":"stopped-after-d-redirects","errorCode":null,"errorMessage":"stopped after %d redirects","messagePattern":"stopped after (.+?) redirects","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/pluginstore/github.go","lineNumber":183,"sourceCode":"\t\t\t\theaders.Del(name)\n\t\t\t}\n\t\t\tif resp != nil && resp.Request != nil {\n\t\t\t\tresp.Request.Header = nil\n\t\t\t}\n\t\t}\n\t\tif errDo != nil {\n\t\t\treturn nil, errDo\n\t\t}\n\t\tif pluginStoreRedirectStatus(resp.StatusCode) {\n\t\t\tnextURL, errRedirect := pluginStoreRedirectURL(resp, currentURL)\n\t\t\tif errClose := resp.Body.Close(); errClose != nil {\n\t\t\t\tlog.WithError(errClose).Debug(\"failed to close plugin store redirect body\")\n\t\t\t}\n\t\t\tif errRedirect != nil {\n\t\t\t\treturn nil, errRedirect\n\t\t\t}\n\t\t\tif redirects >= maxPluginStoreRedirects {\n\t\t\t\treturn nil, fmt.Errorf(\"stopped after %d redirects\", maxPluginStoreRedirects)\n\t\t\t}\n\t\t\tcurrentURL = nextURL\n\t\t\tcontinue\n\t\t}\n\t\treturn readPluginStoreResponse(resp, maxSize, authenticated)\n\t}\n}\n\nfunc (c Client) httpClient() HTTPDoer {\n\tif c.HTTPClient != nil {\n\t\treturn c.HTTPClient\n\t}\n\treturn http.DefaultClient\n}\n\nfunc (c Client) userAgent() string {\n\tif strings.TrimSpace(c.UserAgent) != \"\" {\n\t\treturn strings.TrimSpace(c.UserAgent)","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/github.go#L165-L201","documentation":"The plugin store's HTTP getter follows 3xx redirects manually (statuses 301/302/303/307/308) with a counter capped at maxPluginStoreRedirects = 10. 'stopped after %d redirects' is returned when a redirect is received and the counter is already at the limit — the client refuses to follow an eleventh hop, protecting against redirect loops.","triggerScenarios":"Any plugin store GET (metadata or artifact) whose redirect chain exceeds 10 hops: an owner/repo redirect loop on GitHub, a CDN misconfiguration bouncing between two URLs, or auth flows that keep redirecting because credentials are not attached.","commonSituations":"A renamed GitHub repository whose release asset URLs redirect through multiple hops; two mirrors pointing at each other; S3/CloudFront presigned-URL chains that expire and redirect to a login page that redirects back.","solutions":["Resolve the final artifact URL once with curl -IL and update the manifest/URL to the redirect target to shorten the chain.","If a loop is involved (URL A -> B -> A), fix the server-side redirect or the misconfigured mirror.","For private repos, ensure GitHub auth is configured so the API download URL is used directly instead of bouncing through sign-in redirects (see releaseAssetAPIAuthenticated).","Re-publish assets under stable, direct URLs."],"exampleFix":"# before\n# manifest url bounces through 11 redirects (mirror loop)\ncurl -IL https://mirror.example.com/p-linux-amd64  # ends in a loop\n\n# after\n# resolve the final URL and pin it in the manifest\ncurl -IL https://mirror.example.com/p-linux-amd64 | grep -i '^location'\n# manifest: url: https://final-host.example.com/p-linux-amd64","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"data, err := client.get(ctx, artifact.URL, \"application/octet-stream\", kind, 0)\nif err != nil {\n    if strings.Contains(err.Error(), \"stopped after\") && strings.Contains(err.Error(), \"redirects\") {\n        // resolve and pin the final URL, then retry once\n        final := resolveFinalURL(ctx, artifact.URL)\n        if final != \"\" && final != artifact.URL {\n            artifact.URL = final\n            data, err = client.get(ctx, artifact.URL, \"application/octet-stream\", kind, 0)\n        }\n    }\n    if err != nil { return err }\n}","preventionTips":["Pin final (post-redirect) URLs in manifests.","Keep redirect chains short; avoid chains of mirrors.","Configure auth for private repos so sign-in redirects do not loop."],"tags":["redirects","http","loop-protection","network","plugin-store"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}