Kareadita/Kavita · error · KavitaException
No download URL available for {filePath}
Error message
No download URL available for {filePath} What it means
Thrown by CblGithubService.GetFileContent after fetching a GitHub content item whose download_url is null or empty. GitHub's contents API omits download_url for some object types (e.g. directories, symlinks, or submodule entries) or returns null when the repo moved/renamed the file. Kavita needs a raw download URL to fetch the file body, so an empty one is treated as an unrecoverable fetch failure.
Source
Thrown at Kavita.Services/ReadingLists/CblGithubService.cs:145
var item = await BuildApiUrl(normalizedPath)
.WithGithubHeaders()
.GetJsonAsync<GithubContentItem>();
return item.Sha;
}
public async Task<string> GetFileContent(string filePath)
{
var normalizedPath = NormalizePath(filePath);
var item = await BuildApiUrl(normalizedPath)
.WithGithubHeaders()
.GetJsonAsync<GithubContentItem>();
if (string.IsNullOrEmpty(item.DownloadUrl))
{
throw new KavitaException($"No download URL available for {filePath}");
}
return await DownloadByUrl(item.DownloadUrl);
}
/// <summary>
/// Downloads the content of a file
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
private static async Task<string> DownloadByUrl(string url)
{
return await url
.WithGithubHeaders()
.GetStringAsync();
}
private async Task<(List<CblRepoItemDto> Items, GithubRateLimitDto RateLimit)> FetchDirectoryFromGithub(string path)View on GitHub (pinned to 9c3e540000)
Solutions
- Verify filePath points to an actual .cbl/.json file and not a directory using BrowseRepo's IsDirectory flag before calling GetFileContent.
- If the file is large, fetch it via the Git blobs API with the sha instead of relying on download_url.
- Retry once after a short delay in case the null download_url is a transient GitHub inconsistency.
- Check the CBL-ReadingLists repo in a browser to confirm the path still resolves to a file with a raw link.
Example fix
// before
var content = await cblGithubService.GetFileContent(filePath);
// after
var item = await repo.BrowseRepo(parentDir);
var target = item.Items.SingleOrDefault(i => i.Path == filePath);
if (target is null || target.IsDirectory || string.IsNullOrEmpty(target.DownloadUrl))
return BadRequest($"{filePath} has no downloadable raw content");
var content = await cblGithubService.GetFileContent(filePath); Defensive patterns
Strategy: validation
Validate before calling
var browse = await cblGithubService.BrowseRepo(Path.GetDirectoryName(filePath));
var item = browse.Items.FirstOrDefault(i => i.Path == filePath);
if (item is null || item.IsDirectory || string.IsNullOrEmpty(item.DownloadUrl))
return BadRequest($"{filePath} is not downloadable");
var content = await cblGithubService.GetFileContent(filePath); Type guard
static bool IsDownloadable(CblRepoItemDto item) => !item.IsDirectory && !string.IsNullOrEmpty(item.DownloadUrl);
Try / catch
try { return await cblGithubService.GetFileContent(filePath); }
catch (KavitaException ex) when (ex.Message.Contains("No download URL"))
{ return NotFound($"{filePath} has no raw download URL"); } Prevention
- Gate GetFileContent behind a BrowseRepo lookup that confirms a downloadable file.
- Avoid calling GetFileContent on directory entries.
- Retry once for transient null download_url from GitHub.
When it happens
Trigger: Calling GetFileContent(filePath) where filePath resolves to a directory entry, a submodule, a file >100MB (where GitHub requires the blob API), or a path whose content item's download_url field came back null from the REST API.
Common situations: User clicks a folder row in the CBL browser that was mislabeled as a file; the CBL repo added a submodule; a file was force-pushed and the API temporarily returns null download_url; or a path with special characters was normalized incorrectly so it points at the wrong object.
Related errors
- GitHub API rate limit exhausted.{resetsIn} Cached data may s
- 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/c32ca37eea059e54.
Report an issue: GitHub.