{"record":{"id":"b3a44fee623ef864","repo":"router-for-me/CLIProxyAPI","slug":"redirect-missing-location-header","errorCode":null,"errorMessage":"redirect missing Location header","messagePattern":"redirect missing Location header","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/pluginstore/github.go","lineNumber":246,"sourceCode":"\tclone.CheckRedirect = func(*http.Request, []*http.Request) error {\n\t\treturn http.ErrUseLastResponse\n\t}\n\treturn &clone\n}\n\nfunc pluginStoreRedirectStatus(status int) bool {\n\tswitch status {\n\tcase http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect, http.StatusPermanentRedirect:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc pluginStoreRedirectURL(resp *http.Response, requestURL string) (string, error) {\n\tlocation := strings.TrimSpace(resp.Header.Get(\"Location\"))\n\tif location == \"\" {\n\t\treturn \"\", fmt.Errorf(\"redirect missing Location header\")\n\t}\n\tbase, errBase := url.Parse(requestURL)\n\tif errBase != nil {\n\t\treturn \"\", fmt.Errorf(\"parse redirect base: %w\", errBase)\n\t}\n\tnext, errNext := base.Parse(location)\n\tif errNext != nil {\n\t\treturn \"\", fmt.Errorf(\"parse redirect location: %w\", errNext)\n\t}\n\tif next.Scheme == \"\" || next.Host == \"\" {\n\t\treturn \"\", fmt.Errorf(\"redirect location is not absolute\")\n\t}\n\treturn next.String(), nil\n}\n\nfunc readPluginStoreResponse(resp *http.Response, maxSize int64, authenticated bool) ([]byte, error) {\n\tdefer func() {\n\t\tif errClose := resp.Body.Close(); errClose != nil {","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/github.go#L228-L264","documentation":"pluginStoreRedirectURL extracts the next hop from a 3xx response. 'redirect missing Location header' means the response carried a redirect status (301/302/303/307/308) but its Location header was absent or whitespace-only after TrimSpace. The manual redirect follower cannot proceed without a target.","triggerScenarios":"Any followed 3xx whose headers lack Location — misconfigured servers, some load balancers returning 302 with the target in a custom header, or empty-header edge cases from certain CDNs.","commonSituations":"A reverse proxy or auth middleware issuing a 302 without Location; a health/maintenance page returned as 302 with no target; unusual GitHub Enterprise configurations.","solutions":["Reproduce with curl -I against the URL and inspect the 3xx response headers to confirm Location is truly missing.","Fix or report the server/proxy that emits redirect statuses without a Location header.","Bypass the broken hop by pinning the final artifact URL in the plugin manifest."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := fetch(); err != nil {\n    if strings.Contains(err.Error(), \"redirect missing Location\") {\n        // server bug: log the hop URL and pin a direct artifact URL instead of retrying blindly\n    }\n}","preventionTips":["curl -I the failing hop to confirm the missing header.","Avoid hosts/proxies that emit 3xx without Location."],"tags":["http","redirects","headers","network","plugin-store"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}