{"record":{"id":"d2c43c285fef54ad","repo":"henrygd/beszel","slug":"d-failed-to-fetch-latest-releases-s","errorCode":null,"errorMessage":"(%d) failed to fetch latest releases:\n%s","messagePattern":"\\((.+?)\\) failed to fetch latest releases:\n(.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/ghupdate/ghupdate.go","lineNumber":260,"sourceCode":"\treq, err := http.NewRequestWithContext(ctx, \"GET\", url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer res.Body.Close()\n\n\trawBody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// http.Client doesn't treat non 2xx responses as error\n\tif res.StatusCode >= 400 {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"(%d) failed to fetch latest releases:\\n%s\",\n\t\t\tres.StatusCode,\n\t\t\tstring(rawBody),\n\t\t)\n\t}\n\n\tresult := &release{}\n\tif err := json.Unmarshal(rawBody, result); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result, nil\n}\n\nfunc downloadFile(\n\tctx context.Context,\n\tclient HttpClient,\n\turl string,","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/henrygd/beszel/blob/b38fb7dafa60812cc22e6a84ce313e94f1ce0a32/internal/ghupdate/ghupdate.go#L242-L278","documentation":"FetchLatestRelease queries the GitHub releases API (or a configured base URL). Go's http.Client does not treat non-2xx status codes as errors, so the library explicitly checks and returns this error containing the HTTP status code and the raw response body when the status is >= 400. It means the releases listing request was rejected by the server.","triggerScenarios":"Calling FetchLatestRelease (called from update) when the releases endpoint responds 4xx/5xx — e.g. 404 for a nonexistent repo, 403 from GitHub API rate limiting, 401 from a bad token, or 5xx server errors.","commonSituations":"Hitting GitHub's unauthenticated rate limit (60 req/h) and getting 403 with a rate-limit JSON body; typo in the owner/repo configured for updates; a proxy or corporate firewall returning 4xx; GitHub incidents returning 5xx.","solutions":["Read the status and body in the error message: 403 usually means rate limiting — authenticate with a GITHUB_TOKEN or wait for the rate limit window to reset.","Verify the configured owner/repo (or GitHubEnterprise base URL) actually exists and has releases.","If a token is configured, confirm it is valid and not expired (401/404).","Retry after checking GitHub status (https://www.githubstatus.com) if the status is 5xx."],"exampleFix":"// before: unauthenticated requests get rate-limited\nclient := ghupdate.NewUpdater(...)\nrel, err := client.FetchLatestRelease(ctx) // (403) failed to fetch latest releases: ...rate limit...\n\n// after: provide an authenticated http client\nhttpClient := &http.Client{Transport: &authTransport{token: os.Getenv(\"GITHUB_TOKEN\")}}\nrel, err := clientWithHTTP(httpClient).FetchLatestRelease(ctx)","handlingStrategy":"retry","validationCode":"resp, err := http.Get(baseURL + \"/rate_limit\")\nif err == nil {\n    defer resp.Body.Close()\n    // inspect remaining core requests before calling FetchLatestRelease\n}","typeGuard":"func isRateLimitErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"(403)\") && strings.Contains(err.Error(), \"rate limit\")\n}","tryCatchPattern":"rel, err := updater.FetchLatestRelease(ctx)\nif err != nil {\n    var he interface{ Error() string }\n    if strings.Contains(err.Error(), \"(403)\") { // rate limited\n        time.Sleep(time.Until(resetTime)); rel, err = updater.FetchLatestRelease(ctx)\n    }\n    if err != nil { return fmt.Errorf(\"release check failed: %w\", err) }\n}","preventionTips":["Authenticate GitHub API requests with a token to raise rate limits.","Cache the last release check and back off (don't poll every run).","Validate owner/repo configuration on startup.","Alert on 5xx statuses instead of retrying in a tight loop."],"tags":["network","http","github-api","rate-limit"],"backgroundTag":"http-non-2xx-response","analyzedSha":"b38fb7dafa60812cc22e6a84ce313e94f1ce0a32","analyzedAt":"2026-08-31T15:10:10.149Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}