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

  1. Serve from cache: call BrowseRepo without forceRefresh so the 4-hour CblRepoCache is used instead of the API.
  2. Configure a GitHub personal access token in the app's GithubHeaders so requests authenticate and get the 5000/hour quota.
  3. Wait until the reported ResetsAtUtc time before issuing new CBL requests.
  4. 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

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


AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13). Data as JSON: /api/errors/9dcc75a9f1fafbb3. Report an issue: GitHub.