{"record":{"id":"100f8b55b8bf1159","repo":"Kareadita/Kavita","slug":"series-doesnt-exist","errorCode":null,"errorMessage":"series-doesnt-exist","messagePattern":"series-doesnt-exist","errorType":"exception","errorClass":"KavitaException","httpStatus":null,"severity":"error","filePath":"Kavita.Services/KoreaderService.cs","lineNumber":51,"sourceCode":"            userId, koreaderBookDto.progress.Sanitize(), koreaderBookDto.document.Sanitize());\n        var file = await unitOfWork.MangaFileRepository.GetByKoreaderHash(koreaderBookDto.document, ct);\n        if (file == null)\n        {\n            logger.LogWarning(\"KOReader progress for unknown book: {BookHash}. Run a force scan on the series to generate KOReader hashes\", koreaderBookDto.document.Sanitize());\n            throw new KavitaException(await localizationService.TranslateAsync(userId, \"file-missing\"));\n        }\n\n        var userProgressDto = await unitOfWork.AppUserProgressRepository.GetUserProgressDtoAsync(file.ChapterId, userId, ct);\n        if (userProgressDto == null)\n        {\n            var chapterDto = await unitOfWork.ChapterRepository.GetChapterDtoAsync(file.ChapterId, userId, ct);\n            if (chapterDto == null) throw new KavitaException(await localizationService.TranslateAsync(userId, \"chapter-doesnt-exist\"));\n\n            var volumeDto = await unitOfWork.VolumeRepository.GetVolumeByIdAsync(chapterDto.VolumeId, ct: ct);\n            if (volumeDto == null) throw new KavitaException(await localizationService.TranslateAsync(userId, \"volume-doesnt-exist\"));\n\n            var seriesDto = await unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(volumeDto.SeriesId, userId, ct);\n            if (seriesDto == null) throw new KavitaException(await localizationService.TranslateAsync(userId, \"series-doesnt-exist\"));\n\n            userProgressDto = new ProgressDto()\n            {\n                PageNum = 0, // This is updated in KoreaderHelper.UpdateProgressDto\n                ChapterId = file.ChapterId,\n                VolumeId = chapterDto.VolumeId,\n                SeriesId = seriesDto.Id,\n                LibraryId = seriesDto.LibraryId\n            };\n        }\n\n        // Update the bookScrollId if possible\n        var reportedProgress = koreaderBookDto.progress;\n        KoreaderHelper.UpdateProgressDto(userProgressDto, koreaderBookDto.progress);\n\n        logger.LogDebug(\"Converted KOReader progress from {ProgressEncoding} to Page {PageNum} with ScrollId: {ScrollId}. For Chapter {ChapterId} in Series {SeriesId}\",\n            reportedProgress.Sanitize(), userProgressDto.PageNum, userProgressDto.BookScrollId?.Sanitize() ?? string.Empty,\n            userProgressDto.ChapterId, userProgressDto.SeriesId);","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/KoreaderService.cs#L33-L69","documentation":"Thrown in the same rebuild branch: the volume exists and carries a SeriesId, but GetSeriesDtoByIdAsync returns null. The volume points at a missing series — the top of the orphan chain. Reaching here means chapter and volume resolved but series did not, which is rare and indicates a broken volume → series link.","triggerScenarios":"KOReader SaveProgress with a volume whose SeriesId is absent from the series table (deleted series with orphaned volumes, partial restore).","commonSituations":"Series deleted but volumes/chapters left behind; interrupted scan/refresh left the hierarchy half-built; manual DB surgery removed a series without cascading.","solutions":["Force-scan the library to rebuild the full series → volume → chapter → file hierarchy.","Check the DB for volumes whose SeriesId has no matching series row; clean up or reparent.","If the series was deleted intentionally, ensure its volumes/chapters/manga_files were also removed.","Surface a clear 'rescan required' message to the end user rather than retrying the sync."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"var series = await unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(volume.SeriesId, userId, ct);\nif (series == null)\n    return Conflict(new { error = \"series-doesnt-exist\", hint = \"Rescan the library.\" });","typeGuard":null,"tryCatchPattern":"try { await koreaderService.SaveProgress(dto, userId, ct); }\ncatch (KavitaException ex) when (ex.Message.Contains(\"series-doesnt-exist\"))\n{ /* full library rescan; do not retry until hierarchy rebuilt */ }","preventionTips":["Cascade-delete volumes/chapters/files when a series is removed.","Run library scans after bulk imports/deletes.","Monitor for volumes whose SeriesId is orphaned."],"tags":["koreader","data-integrity","series","scan"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}