Kareadita/Kavita · error · KavitaException

chapter-doesnt-exist

chapter-doesnt-exist

Error message

chapter-doesnt-exist

What it means

Thrown by AnnotationService.CreateAnnotation when ChapterRepository.GetChapterAsync(dto.ChapterId) returns null — i.e. no Chapter row exists for the supplied ChapterId. Like error 20, this throw lives inside the outer try and is caught/re-wrapped as 'annotation-failed-create' (line 98 catch), so the caller never sees 'chapter-doesnt-exist' directly. The distinct code is reachable but masked; check server logs for the underlying exception.

Source

Thrown at Kavita.Services/AnnotationService.cs:55

    /// <summary>
    /// Create a new Annotation for the user against a Chapter
    /// </summary>
    /// <param name="userId"></param>
    /// <param name="dto"></param>
    /// <param name="ct"></param>
    /// <returns></returns>
    /// <exception cref="KavitaException">Message is not localized</exception>
    public async Task<AnnotationDto> CreateAnnotation(int userId, AnnotationDto dto, CancellationToken ct = default)
    {
        try
        {
            if (dto.HighlightCount == 0 || string.IsNullOrWhiteSpace(dto.SelectedText))
            {
                throw new KavitaException("invalid-payload");
            }

            var chapter = await unitOfWork.ChapterRepository.GetChapterAsync(dto.ChapterId, ct: ct) ?? throw new KavitaException("chapter-doesnt-exist");
            var chapterTitle = string.Empty;

            try
            {
                var toc = await bookService.GenerateTableOfContents(chapter);
                var pageTocs = BookChapterItemHelper.GetTocForPage(toc, dto.PageNumber);
                if (pageTocs.Count > 0)
                {
                    chapterTitle = pageTocs[0].Title;
                }
            }
            catch (KavitaException)
            {
                /* Swallow */
            }

            var annotation = new AppUserAnnotation()
            {

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Have the client refresh chapter metadata before creating an annotation when the reader has been open a long time.
  2. Validate dto.ChapterId against the user's accessible chapters in the controller/service before the create (return 404/400 explicitly).
  3. Stop masking the exception: catch KavitaException separately and rethrow so 'chapter-doesnt-exist' propagates to the API instead of 'annotation-failed-create'.

Example fix

// before
var chapter = await unitOfWork.ChapterRepository.GetChapterAsync(dto.ChapterId, ct: ct)
    ?? throw new KavitaException("chapter-doesnt-exist");
// ...this is inside try{} and caught by catch(Exception) -> rethrown as annotation-failed-create

// after — move the lookup before the outer try, or rethrow KavitaException
catch (KavitaException) { throw; }
catch (Exception ex) { logger.LogError(ex, ...); throw new KavitaException("annotation-failed-create"); }
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the chapter exists and is accessible before creating
var exists = await unitOfWork.ChapterRepository.GetChapterAsync(dto.ChapterId, ct: ct);
if (exists is null) return NotFound($"Chapter {dto.ChapterId} not found");

Prevention

When it happens

Trigger: POST /api/annotation/create referencing a ChapterId that was deleted, never existed, or is out of range; stale client state holding a ChapterId from a removed series; a typo/manipulated id in the request body.

Common situations: Library was rescanned and chapters were merged/removed while the book reader was open; the chapter belongs to a library the user no longer has access to (the dto is constructed client-side from cached ids).

Related errors


AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13). Data as JSON: /api/errors/1c433c53b39a4953. Report an issue: GitHub.