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
- Check the HTTP status code before deserializing; only parse the body when status is 200 OK.
- Check GitHub API rate limit (curl -s https://api.github.com/rate_limit) and wait for the window to reset; consider authenticating requests.
- Delete the cached releases file so the next check re-downloads fresh release data.
- 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
- Only deserialize the release body after confirming HTTP 200
- Handle GitHub rate-limit responses (403/429) with backoff before parsing
- Keep the on-disk releases cache as a fallback when the API fails
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
- Could not deserialize manifest.json.
- Could not deserialize GOGCatalogResponse for url
- Could not deserialize GOGEmbedFilteredResponse for url
- DownloadFileToStreamAsync returned false.
- dlss_presets.json is empty or invalid.
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)