Kareadita/Kavita · error · KavitaException
volume-doesnt-exist
Error message
volume-doesnt-exist
What it means
Thrown in the same rebuild branch of KoreaderService.SaveProgress: the chapter was found and has a VolumeId, but GetVolumeByIdAsync returns null. The chapter references a volume that doesn't exist — a deeper orphan in the chapter → volume chain. Only reachable when user progress is missing for the chapter.
Source
Thrown at Kavita.Services/KoreaderService.cs:48
public async Task SaveProgress(KoreaderBookDto koreaderBookDto, int userId, CancellationToken ct = default)
{
logger.LogDebug("Saving KOReader progress for User ({UserId}): {KoreaderProgress} - {KoreaderHash}",
userId, koreaderBookDto.progress.Sanitize(), koreaderBookDto.document.Sanitize());
var file = await unitOfWork.MangaFileRepository.GetByKoreaderHash(koreaderBookDto.document, ct);
if (file == null)
{
logger.LogWarning("KOReader progress for unknown book: {BookHash}. Run a force scan on the series to generate KOReader hashes", koreaderBookDto.document.Sanitize());
throw new KavitaException(await localizationService.TranslateAsync(userId, "file-missing"));
}
var userProgressDto = await unitOfWork.AppUserProgressRepository.GetUserProgressDtoAsync(file.ChapterId, userId, ct);
if (userProgressDto == null)
{
var chapterDto = await unitOfWork.ChapterRepository.GetChapterDtoAsync(file.ChapterId, userId, ct);
if (chapterDto == null) throw new KavitaException(await localizationService.TranslateAsync(userId, "chapter-doesnt-exist"));
var volumeDto = await unitOfWork.VolumeRepository.GetVolumeByIdAsync(chapterDto.VolumeId, ct: ct);
if (volumeDto == null) throw new KavitaException(await localizationService.TranslateAsync(userId, "volume-doesnt-exist"));
var seriesDto = await unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(volumeDto.SeriesId, userId, ct);
if (seriesDto == null) throw new KavitaException(await localizationService.TranslateAsync(userId, "series-doesnt-exist"));
userProgressDto = new ProgressDto()
{
PageNum = 0, // This is updated in KoreaderHelper.UpdateProgressDto
ChapterId = file.ChapterId,
VolumeId = chapterDto.VolumeId,
SeriesId = seriesDto.Id,
LibraryId = seriesDto.LibraryId
};
}
// Update the bookScrollId if possible
var reportedProgress = koreaderBookDto.progress;
KoreaderHelper.UpdateProgressDto(userProgressDto, koreaderBookDto.progress);
View on GitHub (pinned to 9c3e540000)
Solutions
- Force-scan the series to rebuild the volume → chapter → file graph.
- Inspect the DB for chapters whose VolumeId has no matching volume row and repair or remove them.
- If the volume was intentionally deleted, the chapter should have been re-homed; re-add the source files and scan.
- Catch KavitaException upstream and advise the user to rescan rather than silently dropping progress.
Defensive patterns
Strategy: try-catch
Validate before calling
var volume = await unitOfWork.VolumeRepository.GetVolumeByIdAsync(chapter.VolumeId, ct: ct);
if (volume == null)
return Conflict(new { error = "volume-doesnt-exist", hint = "Rescan the series." }); Try / catch
try { await koreaderService.SaveProgress(dto, userId, ct); }
catch (KavitaException ex) when (ex.Message.Contains("volume-doesnt-exist"))
{ /* request a rescan; degrade gracefully rather than retry in a loop */ } Prevention
- Rescan after volume/special reorganization.
- Audit chapters whose VolumeId has no matching volume row.
- Treat hierarchy-integrity errors as 'rescan required', not transient.
When it happens
Trigger: KOReader progress sync where Chapter.VolumeId points at a deleted volume row; happens after a volume restructure (e.g. special/normal volume recategorization) that orphaned chapters.
Common situations: Library scan reorganized volumes and left chapters pointing at removed volume IDs; partial DB restore or interrupted migration.
Related errors
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/8c73d0ade27b4af7.
Report an issue: GitHub.