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
- Validate that title is non-empty/non-whitespace before calling create (reject early with reading-list-title-required semantics).
- Confirm the AppUser passed in is tracked by the same DbContext/unitOfWork.
- Inspect ReadingListBuilder.Build() to ensure it sets at least Title/NormalizedTitle so EF sees a change.
- 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
- Require non-empty title in the create form.
- Ensure the AppUser is tracked by the same unitOfWork.
- Verify ReadingListBuilder.Build sets Title so HasChanges is true.
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
- reading-list-name-exists
- reading-list-title-required
- {comparison} is not applicable for {fieldName}
- Name must be set
- You cannot use the name of a system provided stream
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/b90c250529f5fd70.
Report an issue: GitHub.