Kareadita/Kavita · error · KavitaException
series-doesnt-exist
Error message
series-doesnt-exist
What it means
Thrown by MarkChaptersAsRead when GetSeriesByIdAsync(seriesId) returns null. The literal 'series-doesnt-exist' is a localization key surfaced to the UI. It guards a core read-progress mutation: if the series no longer exists, progress rows cannot be attached to it.
Source
Thrown at Kavita.Services/Reading/ReaderService.cs:95
user.Progresses ??= new List<AppUserProgress>();
foreach (var volume in volumes)
{
await MarkChaptersAsUnread(user, seriesId, volume.Chapters);
}
}
/// <summary>
/// Marks all Chapters as Read by creating or updating UserProgress rows. Does not commit.
/// </summary>
/// <remarks>Emits events to the UI for each chapter progress and one for each volume progress</remarks>
/// <param name="user"></param>
/// <param name="seriesId"></param>
/// <param name="chapters"></param>
public async Task MarkChaptersAsRead(AppUser user, int seriesId, IList<Chapter> chapters)
{
var seenVolume = new Dictionary<int, bool>();
var series = await unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
if (series == null) throw new KavitaException("series-doesnt-exist");
foreach (var chapter in chapters)
{
var userProgress = GetUserProgressForChapter(user, chapter);
if (userProgress == null)
{
user.Progresses.Add(new AppUserProgress
{
PagesRead = chapter.Pages,
VolumeId = chapter.VolumeId,
SeriesId = seriesId,
ChapterId = chapter.Id,
LibraryId = series.LibraryId,
});
}
else
{View on GitHub (pinned to 9c3e540000)
Solutions
- Refresh the library list in the client to confirm the series still exists and is in an accessible library.
- Verify the seriesId sent matches an existing Series row (and the user has library access).
- If the series was deleted, discard the stale client reference and stop the mark-read call.
- Trigger a library scan to ensure the series is re-imported if it should exist.
Example fix
// before
await readerService.MarkChaptersAsRead(user, seriesId, chapters);
// after: validate existence first
var series = await unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
if (series == null) return NotFound("series-doesnt-exist");
await readerService.MarkChaptersAsRead(user, seriesId, chapters); Defensive patterns
Strategy: validation
Validate before calling
var series = await unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId); if (series == null) return NotFound(); await readerService.MarkChaptersAsRead(user, seriesId, chapters);
Type guard
public static bool SeriesExists(Series? s) => s is not null;
Try / catch
try { await readerService.MarkChaptersAsRead(user, seriesId, chapters); }
catch (KavitaException ex) when (ex.Message == "series-doesnt-exist")
{ return NotFound("Series no longer exists"); } Prevention
- Validate seriesId against the DB (and user library access) before mutating progress.
- Discard stale client references after library rescans/deletions.
- Centralize series-existence checks in the API layer so callers can't pass dead ids.
When it happens
Trigger: A client requests marking chapters read for a seriesId that was deleted, that belongs to a library the user lacks access to (so the query returns nothing), or a stale/cached seriesId from before a library rescan that removed it.
Common situations: User had a series open in the web reader while an admin deleted it or rescanned it away; a third-party client cached an old series id; the series was moved between libraries and the old id reference is stale.
Related errors
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/1ad65228a27ad864.
Report an issue: GitHub.