henrygd/beszel · error

(%d) failed to fetch latest releases: %s

Error message

(%d) failed to fetch latest releases:
%s

What it means

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.

Source

Thrown at internal/ghupdate/ghupdate.go:260

	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, err
	}

	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	rawBody, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	// http.Client doesn't treat non 2xx responses as error
	if res.StatusCode >= 400 {
		return nil, fmt.Errorf(
			"(%d) failed to fetch latest releases:\n%s",
			res.StatusCode,
			string(rawBody),
		)
	}

	result := &release{}
	if err := json.Unmarshal(rawBody, result); err != nil {
		return nil, err
	}

	return result, nil
}

func downloadFile(
	ctx context.Context,
	client HttpClient,
	url string,

View on GitHub (pinned to b38fb7dafa)

Solutions

  1. 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.
  2. Verify the configured owner/repo (or GitHubEnterprise base URL) actually exists and has releases.
  3. If a token is configured, confirm it is valid and not expired (401/404).
  4. Retry after checking GitHub status (https://www.githubstatus.com) if the status is 5xx.

Example fix

// before: unauthenticated requests get rate-limited
client := ghupdate.NewUpdater(...)
rel, err := client.FetchLatestRelease(ctx) // (403) failed to fetch latest releases: ...rate limit...

// after: provide an authenticated http client
httpClient := &http.Client{Transport: &authTransport{token: os.Getenv("GITHUB_TOKEN")}}
rel, err := clientWithHTTP(httpClient).FetchLatestRelease(ctx)
Defensive patterns

Strategy: retry

Validate before calling

resp, err := http.Get(baseURL + "/rate_limit")
if err == nil {
    defer resp.Body.Close()
    // inspect remaining core requests before calling FetchLatestRelease
}

Type guard

func isRateLimitErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "(403)") && strings.Contains(err.Error(), "rate limit")
}

Try / catch

rel, err := updater.FetchLatestRelease(ctx)
if err != nil {
    var he interface{ Error() string }
    if strings.Contains(err.Error(), "(403)") { // rate limited
        time.Sleep(time.Until(resetTime)); rel, err = updater.FetchLatestRelease(ctx)
    }
    if err != nil { return fmt.Errorf("release check failed: %w", err) }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of henrygd/beszel@b38fb7dafa (2026-08-31). Data as JSON: /api/errors/d2c43c285fef54ad. Report an issue: GitHub.