{"record":{"id":"85d290ae88044eb3","repo":"Kareadita/Kavita","slug":"chapter-doesnt-exist-85d290","errorCode":null,"errorMessage":"chapter-doesnt-exist","messagePattern":"chapter-doesnt-exist","errorType":"exception","errorClass":"OpdsException","httpStatus":null,"severity":"error","filePath":"Kavita.Services/OpdsService.cs","lineNumber":704,"sourceCode":"        var chapterId = request.ChapterId;\n\n        var series = await unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId, ct);\n        if (series == null)\n        {\n            throw new OpdsException(await localizationService.TranslateAsync(userId, \"series-doesnt-exist\"));\n        }\n\n        var volume = await unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId,  userId, ct);\n        if (volume == null)\n        {\n            throw new OpdsException(await localizationService.TranslateAsync(userId, \"volume-doesnt-exist\"));\n        }\n\n        var libraryType = await unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId, ct);\n        var chapter = volume.Chapters.FirstOrDefault(c => c.Id == chapterId);\n        if (chapter == null)\n        {\n            throw new OpdsException(await localizationService.TranslateAsync(userId, \"chapter-doesnt-exist\"));\n        }\n\n        var namingContext = await LocalizedNamingContext.CreateAsync(namingService, localizationService, userId, libraryType);\n        var chapterName = namingContext.FormatChapterTitle(chapter);\n\n        var feed = CreateFeed( $\"{series.Name} - Volume {volume.Name} - {chapterName} {chapterId}\",\n            $\"{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}\", apiKey, prefix);\n        SetFeedId(feed, $\"series-{series.Id}-volume-{volumeId}-{chapterId}-files\");\n\n\n        feed.Entries.Add(CreateChapterFeedEntry(series, volume, chapter, namingContext, request));\n\n        return feed;\n    }\n\n    public async Task<Feed> Search(OpdsSearchRequest request, CancellationToken ct = default)\n    {\n        var userId = UnpackRequest(request, out var apiKey, out var prefix, out var baseUrl);","sourceCodeStart":686,"sourceCodeEnd":722,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/OpdsService.cs#L686-L722","documentation":"Thrown by OpdsService.GetItemsFromChapter when the chapter is not found within the volume's chapter collection. After the series and volume checks pass, the service does volume.Chapters.FirstOrDefault(c => c.Id == chapterId). If this returns null, the chapterId does not belong to the validated volume. This is distinct from a volume-level null: the volume exists but doesn't contain this chapter.","triggerScenarios":"An OPDS client requests {apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}. The series and volume are valid and accessible, but the chapterId doesn't match any chapter in volume.Chapters. This can happen if the chapter was moved to a different volume during rescan, deleted, or the chapterId is from a different volume.","commonSituations":"A library rescan restructured chapters (e.g., a chapter moved from Volume 1 to Volume 2). The chapterId is from a different volume than volumeId. The chapter was a special or extra that got re-categorized. The OPDS reader cached an old URL where the chapter-volume mapping changed.","solutions":["Navigate back to the volume feed in the OPDS client to get current chapter IDs.","Rescan the library if chapters were restructured.","Verify the chapterId belongs to the specified volumeId.","Refresh the OPDS client's cache after library rescans.","Check Kavita's UI to confirm the chapter exists under the expected volume."],"exampleFix":"// No code fix — chapter not in volume.\n// OPDS client: go back to the volume feed and re-select the\n// chapter to get a current URL.\n\n// Verify chapterId is within volumeId's chapters:\n// GET {apiKey}/series/{seriesId}/volume/{volumeId}\n//   -> list of valid chapter IDs in that volume","handlingStrategy":"validation","validationCode":"// Before requesting a chapter feed, verify the full chain:\n// var series = await unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId, ct);\n// if (series == null) { ShowUser(\"Series not found.\"); return; }\n// var volume = await unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, userId, ct);\n// if (volume == null) { ShowUser(\"Volume not found.\"); return; }\n// if (volume.Chapters.All(c => c.Id != chapterId))\n// {\n//     ShowUser(\"Chapter not found in this volume.\");\n//     return;\n// }\n// var feed = await opdsService.GetItemsFromChapter(request, ct);","typeGuard":null,"tryCatchPattern":"// try { var feed = await opdsService.GetItemsFromChapter(request, ct); }\n// catch (OpdsException ex) when (ex.Message.Contains(\"chapter-doesnt-exist\"))\n// {\n//     // Chapter not in this volume (moved or deleted during rescan).\n//     await RefreshVolumeFeed(seriesId, volumeId);\n//     return BadRequest(ex.Message);\n// }","preventionTips":["Verify the chapterId belongs to the specified volumeId before requesting the feed.","Navigate from volume -> chapter to ensure the chapter is current.","Refresh the OPDS feed after library rescans that restructure chapters.","Avoid caching deep OPDS URLs across library rescans.","Handle 'chapter-doesnt-exist' by refreshing the volume feed."],"tags":["opds","chapter","not-found","feed","stale-reference","library-scan"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}