Kareadita/Kavita · error · KavitaException

series-doesnt-exist

Error message

series-doesnt-exist

What it means

Thrown by PromoteImplicitProfile when the series bound to the implicit profile (profileToPromote.SeriesIds[0]) no longer exists in the DB. The code comment 'Shouldn't happen' marks it as an invariant-violation guard: an implicit profile should always reference a live series. The 'series-doesnt-exist' localization key is surfaced.

Source

Thrown at Kavita.Services/Reading/ReadingProfileService.cs:110

        if (profileToPromote.Kind != ReadingProfileKind.Implicit) throw new KavitaException("profile-not-implicit");

        var seriesId = profileToPromote.SeriesIds[0]; // An Implicit series can only be bound to 1 Series

        // Check if there are any reading profiles (Series) already bound to the series (and device)
        var existingSeriesProfile = allUserProfiles
            .Where(rp => rp.Kind == ReadingProfileKind.User)
            .Where(rp => activeDeviceId == null || rp.DeviceIds.Contains(activeDeviceId.Value))
            .FirstOrDefault(rp => rp.SeriesIds.Contains(seriesId));

        if (existingSeriesProfile != null)
        {
            existingSeriesProfile.SeriesIds.Remove(seriesId);
            unitOfWork.AppUserReadingProfileRepository.Update(existingSeriesProfile);
        }

        // Convert the implicit profile into a proper Series
        var series = await unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
        if (series == null) throw new KavitaException("series-doesnt-exist"); // Shouldn't happen

        profileToPromote.Kind = ReadingProfileKind.User;
        profileToPromote.Name = await localizationService.TranslateAsync(userId, "generated-reading-profile-name", series.Name);
        profileToPromote.Name = EnsureUniqueProfileName(allUserProfiles, profileToPromote.Name);
        profileToPromote.NormalizedName = profileToPromote.Name.ToNormalized();
        unitOfWork.AppUserReadingProfileRepository.Update(profileToPromote);

        await unitOfWork.CommitAsync();

        return mapper.Map<UserReadingProfileDto>(profileToPromote);
    }

    private static string EnsureUniqueProfileName(IList<AppUserReadingProfile> allUserProfiles, string name)
    {
        var counter = 1;
        var newName = name;
        while (allUserProfiles.Any(p => p.Name == newName))
        {

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Delete or reset the orphaned implicit profile so it stops referencing the missing series.
  2. Trigger a library scan to confirm series state, then recreate the implicit profile from the reader.
  3. Investigate data integrity if this fires — the comment implies it should be unreachable.
Defensive patterns

Strategy: validation

Validate before calling

var series = await unitOfWork.SeriesRepository.GetSeriesByIdAsync(profileToPromote.SeriesIds[0]);
if (series == null) { // clean up orphaned implicit profile instead of throwing
    unitOfWork.AppUserReadingProfileRepository.Remove(profileToPromote);
    await unitOfWork.CommitAsync();
    return;
}

Try / catch

try { await profileService.PromoteImplicitProfile(userId, profileId, activeDeviceId); }
catch (KavitaException ex) when (ex.Message == "series-doesnt-exist") { _logger.LogError("Orphaned implicit profile {Id}", profileId); return NotFound(); }

Prevention

When it happens

Trigger: The series tied to the implicit profile was deleted between profile creation and promotion, leaving a dangling reference in SeriesIds.

Common situations: Series removed by an admin/library rescan while an implicit profile lingered; data-integrity drift after migration; manual DB edits broke the reference.

Related errors


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