{"record":{"id":"e0dca3466424f80d","repo":"Kareadita/Kavita","slug":"github-api-access-denied-please-try-again-later","errorCode":null,"errorMessage":"GitHub API access denied. Please try again later.","messagePattern":"GitHub API access denied\\. Please try again later\\.","errorType":"exception","errorClass":"KavitaException","httpStatus":400,"severity":"error","filePath":"Kavita.Services/ReadingLists/CblGithubService.cs","lineNumber":220,"sourceCode":"        catch (FlurlHttpException ex) when (ex.StatusCode == 403)\n        {\n            var rateLimit = ex.Call?.Response != null\n                ? ex.Call.Response.GetRateLimit()\n                : new GithubRateLimitDto();\n\n            if (rateLimit.IsExhausted)\n            {\n                var resetsIn = rateLimit.ResetsAtUtc.HasValue\n                    ? $\" Resets at {rateLimit.ResetsAtUtc.Value:HH:mm} UTC.\"\n                    : string.Empty;\n\n                _logger.LogWarning(\"GitHub API rate limit exhausted.{ResetsIn}\", resetsIn);\n                throw new KavitaException(\n                    $\"GitHub API rate limit exhausted.{resetsIn} Cached data may still be available.\");\n            }\n\n            _logger.LogWarning(ex, \"GitHub API returned 403 for CBL repo\");\n            throw new KavitaException(\"GitHub API access denied. Please try again later.\");\n        }\n        catch (FlurlHttpException ex) when (ex.StatusCode == 404)\n        {\n            _logger.LogWarning(\"CBL repo path not found: {Path}\", path.Sanitize());\n            throw new KavitaException($\"Path not found in CBL repository: {path}\");\n        }\n    }\n\n    private CblRepoCache LoadCache()\n    {\n        var cachePath = GetCacheFilePath();\n        if (!File.Exists(cachePath)) return new CblRepoCache();\n\n        try\n        {\n            var json = File.ReadAllText(cachePath);\n            return JsonSerializer.Deserialize<CblRepoCache>(json) ?? new CblRepoCache();\n        }","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/ReadingLists/CblGithubService.cs#L202-L238","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Regenerate the GitHub token with at least 'public_repo' (or no-scope public read) and reconfigure it in GithubHeaders.","Wait ~1 minute and retry to clear a secondary/abuse rate limit.","Check the FlurlHttpException.Call.Response headers/body in logs to confirm whether it is auth, scope, or abuse.","Fall back to the cached CblRepoCache (BrowseRepo without forceRefresh)."],"exampleFix":"// before\nvar item = await cblGithubService.GetFileContent(filePath); // 403 -> access denied\n\n// after\ntry { return await cblGithubService.GetFileContent(filePath); }\ncatch (KavitaException) when (await cblGithubService.HasCacheFor(filePath))\n{\n    return await cblGithubService.GetCachedFileContent(filePath);\n}","handlingStrategy":"retry","validationCode":"// Non-quota 403 can't be reliably pre-validated; ensure token scope instead.\n// Verify token has public_repo read before configuring GithubHeaders.","typeGuard":null,"tryCatchPattern":"try { return await cblGithubService.GetFileContent(filePath); }\ncatch (KavitaException ex) when (ex.Message.Contains(\"access denied\"))\n{ await Task.Delay(TimeSpan.FromSeconds(30)); return await cblGithubService.GetFileContent(filePath); } // one retry for abuse limit","preventionTips":["Use a PAT with correct public read scope.","Throttle requests to avoid GitHub abuse limits.","Inspect FlurlHttpException.Call.Response in logs to classify the 403.","Fall back to cache when the live API is inaccessible."],"tags":["github-api","auth","network","cbl"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}