Kareadita/Kavita · warning · KavitaException
GitHub API rate limit exhausted.{resetsIn} Cached data may s
Error message
GitHub API rate limit exhausted.{resetsIn} Cached data may still be available. What it means
Thrown inside CblGithubService's 403 handler when the parsed GitHub rate limit reports IsExhausted == true. Unauthenticated GitHub calls are capped at 60 requests/hour per IP; authenticated calls at 5000/hour. Once exhausted, every further contents API call returns 403, so Kavita surfaces a user-facing message with the reset time and points to the 4-hour file cache as a fallback data source.
Source
Thrown at Kavita.Services/ReadingLists/CblGithubService.cs:215
.ThenBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
.ToList();
return (result, rateLimit);
}
catch (FlurlHttpException ex) when (ex.StatusCode == 403)
{
var rateLimit = ex.Call?.Response != null
? ex.Call.Response.GetRateLimit()
: new GithubRateLimitDto();
if (rateLimit.IsExhausted)
{
var resetsIn = rateLimit.ResetsAtUtc.HasValue
? $" Resets at {rateLimit.ResetsAtUtc.Value:HH:mm} UTC."
: string.Empty;
_logger.LogWarning("GitHub API rate limit exhausted.{ResetsIn}", resetsIn);
throw new KavitaException(
$"GitHub API rate limit exhausted.{resetsIn} Cached data may still be available.");
}
_logger.LogWarning(ex, "GitHub API returned 403 for CBL repo");
throw new KavitaException("GitHub API access denied. Please try again later.");
}
catch (FlurlHttpException ex) when (ex.StatusCode == 404)
{
_logger.LogWarning("CBL repo path not found: {Path}", path.Sanitize());
throw new KavitaException($"Path not found in CBL repository: {path}");
}
}
private CblRepoCache LoadCache()
{
var cachePath = GetCacheFilePath();
if (!File.Exists(cachePath)) return new CblRepoCache();
View on GitHub (pinned to 9c3e540000)
Solutions
- Serve from cache: call BrowseRepo without forceRefresh so the 4-hour CblRepoCache is used instead of the API.
- Configure a GitHub personal access token in the app's GithubHeaders so requests authenticate and get the 5000/hour quota.
- Wait until the reported ResetsAtUtc time before issuing new CBL requests.
- Throttle/invalidate less: stop calling InvalidateCache or forceRefresh in loops.
Example fix
// before var result = await cblGithubService.BrowseRepo(path, forceRefresh: true); // after - prefer cache, only force refresh when user explicitly requests it var result = await cblGithubService.BrowseRepo(path, forceRefresh: false); // surface result.FromCache to the UI so users know stale data is being shown
Defensive patterns
Strategy: fallback
Validate before calling
// Prefer cache to avoid burning the quota var result = await cblGithubService.BrowseRepo(path, forceRefresh: false); if (result.FromCache) return Ok(result); // cached, no API call // only force refresh when cache is empty/expired AND user requested it
Try / catch
try { return await cblGithubService.BrowseRepo(path, forceRefresh); }
catch (KavitaException ex) when (ex.Message.Contains("rate limit"))
{ return await cblGithubService.BrowseRepo(path, forceRefresh: false); /* serve cache */ } Prevention
- Default to forceRefresh: false so the 4-hour cache absorbs traffic.
- Configure a GitHub PAT in GithubHeaders to lift the 60/hour anonymous cap.
- Avoid InvalidateCache / forceRefresh in loops.
- Surface reset time to users so they know when to retry.
When it happens
Trigger: Any BrowseRepo(forceRefresh: true) or GetFileContent/GetFileSha call that triggers FetchDirectoryFromGithub after the hourly quota is depleted, producing a FlurlHttpException with StatusCode 403 and an X-RateLimit-Remaining of 0.
Common situations: Running Kavita behind a NAT/proxy where many users share one egress IP (anonymous quota burns fast); rapid cache invalidation or many forceRefresh requests; an automated scanner loop hitting the CBL browser; or no GitHub token configured so the 60/hour anonymous cap applies.
Related errors
- No download URL available for {filePath}
- GitHub API access denied. Please try again later.
- Path not found in CBL repository: {path}
- url-blocked-address
- Kavita has already tried to fetch from {sanitizedBaseUrl} an
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/9dcc75a9f1fafbb3.
Report an issue: GitHub.