Kareadita/Kavita · error · KavitaException

generic-reading-list-create

generic-reading-list-create

Error message

generic-reading-list-create

What it means

Thrown by CreateReadingListForUser when unitOfWork.HasChanges() is false right after the ReadingListBuilder built the list and added it to the user. A non-empty title should always produce a change, so a 'no changes' result means the builder produced an entity that EF tracks as unchanged - typically because the title was whitespace/empty or the builder produced a default instance with no tracked mutations.

Source

Thrown at Kavita.Services/ReadingLists/ReadingListService.cs:65

    /// </summary>
    /// <param name="userWithReadingList"></param>
    /// <param name="title"></param>
    /// <returns></returns>
    /// <exception cref="KavitaException"></exception>
    public async Task<ReadingList> CreateReadingListForUser(AppUser userWithReadingList, string title)
    {
        // When creating, we need to make sure Title is unique
        var normalizedTitle = title.ToNormalized();
        var hasExisting = userWithReadingList.ReadingLists.Any(l => l.NormalizedTitle == normalizedTitle);
        if (hasExisting)
        {
            throw new KavitaException("reading-list-name-exists");
        }

        var readingList = new ReadingListBuilder(title).Build();
        userWithReadingList.ReadingLists.Add(readingList);

        if (!unitOfWork.HasChanges()) throw new KavitaException("generic-reading-list-create");
        await unitOfWork.CommitAsync();
        return readingList;
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="readingList"></param>
    /// <param name="dto"></param>
    /// <exception cref="KavitaException"></exception>
    public async Task UpdateReadingList(ReadingList readingList, UpdateReadingListDto dto)
    {
        dto.Title = dto.Title.Trim();
        if (string.IsNullOrEmpty(dto.Title)) throw new KavitaException("reading-list-title-required");

        if (!dto.Title.Equals(readingList.Title) && await unitOfWork.ReadingListRepository.ReadingListExists(dto.Title, readingList.Id))
            throw new KavitaException("reading-list-name-exists");

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Validate that title is non-empty/non-whitespace before calling create (reject early with reading-list-title-required semantics).
  2. Confirm the AppUser passed in is tracked by the same DbContext/unitOfWork.
  3. Inspect ReadingListBuilder.Build() to ensure it sets at least Title/NormalizedTitle so EF sees a change.
  4. Unit-test the builder with a known title to assert unitOfWork.HasChanges() is true.

Example fix

// before
var list = await readingListService.CreateReadingListForUser(user, title);

// after
if (string.IsNullOrWhiteSpace(title))
    return BadRequest("Title is required");
var list = await readingListService.CreateReadingListForUser(user, title.Trim());
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrWhiteSpace(title))
    return BadRequest("Title is required");
var list = await readingListService.CreateReadingListForUser(user, title.Trim());

Type guard

static bool IsValidListTitle(string? title) => !string.IsNullOrWhiteSpace(title);

Prevention

When it happens

Trigger: CreateReadingListForUser called with a title that, after building and adding, leaves the DbContext with zero pending changes - e.g. an empty/whitespace title that the builder ignored, or a builder misconfiguration that produced an already-tracked/default entity.

Common situations: Frontend sent an empty or whitespace-only title that slipped past the uniqueness check (empty string is unique the first time); a ReadingListBuilder refactor stopped setting a tracked field; or the user entity passed in was detached so the Add produced no change.

Related errors


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