beeradmoore/dlss-swapper · error · Exception

Could not load GitHub release data.

Error message

Could not load GitHub release data.

What it means

FetchLatestRelease downloads the GitHub releases JSON into a memory stream and deserializes it with the source-generated JsonSerializer (SourceGenerationContext.Default.GitHubRelease). If deserialization succeeds but returns null, the code throws because there is no usable release data to compare versions or save to the releases cache file.

Solutions

  1. Check the HTTP status code before deserializing; only parse the body when status is 200 OK.
  2. Check GitHub API rate limit (curl -s https://api.github.com/rate_limit) and wait for the window to reset; consider authenticating requests.
  3. Delete the cached releases file so the next check re-downloads fresh release data.
  4. Log the raw response body on failure to see what GitHub actually returned, and update the GitHubRelease model if the API shape changed.

Example fix

// before
var githubRelease = JsonSerializer.Deserialize(memoryStream, SourceGenerationContext.Default.GitHubRelease);
if (githubRelease is null)
    throw new Exception("Could not load GitHub release data.");
// after
if (response.StatusCode != HttpStatusCode.OK)
    throw new Exception($"GitHub API returned {response.StatusCode}; cannot load release data.");
var githubRelease = JsonSerializer.Deserialize(memoryStream, SourceGenerationContext.Default.GitHubRelease);
if (githubRelease is null)
    throw new Exception("Could not load GitHub release data.");
Defensive patterns

Strategy: try-catch

Validate before calling

// Check HTTP status and rate limit before deserializing
if (response.StatusCode == HttpStatusCode.Forbidden)
{
    // likely GitHub API rate limit; abort or back off
}

Type guard

bool IsValidRelease(GitHubRelease? r) => r is not null && !string.IsNullOrEmpty(r.TagName);

Try / catch

try
{
    var release = await updater.FetchLatestRelease();
}
catch (Exception ex) when (ex.Message == "Could not load GitHub release data.")
{
    logger.LogWarning(ex, "GitHub release check failed; using cached release info");
}

Prevention

When it happens

Trigger: The GitHub API returns a body that deserializes to null for GitHubRelease - e.g. an empty or non-object JSON body (rate-limit message, error JSON) that JsonSerializer.Deserialize accepts but cannot map to a GitHubRelease instance.

Common situations: GitHub API rate limiting (403 with a JSON error body), a proxy or captive portal returning an empty body or HTML, GitHub incident/outage, or an unexpectedly shaped API response after a GitHub API change.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of beeradmoore/dlss-swapper@ab9b1e2d4b (2026-09-15). Data as JSON: /api/errors/e296a9c65d0581bb. Report an issue: GitHub.

Appendix: source

Thrown at src/Data/GitHub/GitHubUpdater.cs:71

                }
            }
        }

        if (shouldDownload == true || forceCheck == true)
        {
            try
            {
                using (var memoryStream = new MemoryStream())
                {
                    var fileDownloader = new FileDownloader("https://api.github.com/repos/beeradmoore/dlss-swapper/releases/latest", 0);
                    await fileDownloader.DownloadFileToStreamAsync(memoryStream).ConfigureAwait(false);

                    memoryStream.Position = 0;

                    var githubRelease = JsonSerializer.Deserialize(memoryStream, SourceGenerationContext.Default.GitHubRelease);
                    if (githubRelease is null)
                    {
                        throw new Exception("Could not load GitHub release data.");
                    }

                    memoryStream.Position = 0;

                    // If we did load the json, save it to disk.
                    using (var fileStream = File.Create(releasesFile))
                    {
                        await memoryStream.CopyToAsync(fileStream).ConfigureAwait(false);
                    }

                    return githubRelease;
                }
            }
            catch (Exception err)
            {
                // NOOP
                Logger.Error(err);
                return null;

View on GitHub (pinned to ab9b1e2d4b)