Kareadita/Kavita · error · KavitaException

GitHub API access denied. Please try again later.

Error message

GitHub API access denied. Please try again later.

What it means

Thrown in the same 403 handler as the rate-limit error, but only when rateLimit.IsExhausted is false. This means GitHub returned 403 for a non-quota reason: the token lacks scope, the repo access was revoked, an abuse/secondary rate limit triggered, or the IP is blocked. Kavita cannot distinguish these from the response, so it surfaces a generic 'access denied, try later' message.

Source

Thrown at Kavita.Services/ReadingLists/CblGithubService.cs:220

        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();

        try
        {
            var json = File.ReadAllText(cachePath);
            return JsonSerializer.Deserialize<CblRepoCache>(json) ?? new CblRepoCache();
        }

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Regenerate the GitHub token with at least 'public_repo' (or no-scope public read) and reconfigure it in GithubHeaders.
  2. Wait ~1 minute and retry to clear a secondary/abuse rate limit.
  3. Check the FlurlHttpException.Call.Response headers/body in logs to confirm whether it is auth, scope, or abuse.
  4. Fall back to the cached CblRepoCache (BrowseRepo without forceRefresh).

Example fix

// before
var item = await cblGithubService.GetFileContent(filePath); // 403 -> access denied

// after
try { return await cblGithubService.GetFileContent(filePath); }
catch (KavitaException) when (await cblGithubService.HasCacheFor(filePath))
{
    return await cblGithubService.GetCachedFileContent(filePath);
}
Defensive patterns

Strategy: retry

Validate before calling

// Non-quota 403 can't be reliably pre-validated; ensure token scope instead.
// Verify token has public_repo read before configuring GithubHeaders.

Try / catch

try { return await cblGithubService.GetFileContent(filePath); }
catch (KavitaException ex) when (ex.Message.Contains("access denied"))
{ await Task.Delay(TimeSpan.FromSeconds(30)); return await cblGithubService.GetFileContent(filePath); } // one retry for abuse limit

Prevention

When it happens

Trigger: FetchDirectoryFromGithub gets a FlurlHttpException with StatusCode 403 whose X-RateLimit-Remaining is non-zero. Common when a configured PAT lacks 'public_repo' read scope, when GitHub triggered an abuse detection limit, or when the CBL repo visibility changed.

Common situations: An expired or scope-stripped personal access token; corporate proxy/firewall rewriting GitHub responses; GitHub's abuse rate limiting after bursts of requests that are individually under quota; or the DieselTech/CBL-ReadingLists repo was made private.

Understand the failure class

Related errors


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