Kareadita/Kavita · error · KavitaException
reading-profiles-device-overlap
Error message
reading-profiles-device-overlap
What it means
Thrown by the private DeviceOverlapGuard in ReadingProfileService, invoked from three create/update paths (lines ~196, 220, 252). It scans the selected set of User reading profiles and rejects the batch when any two distinct profiles share at least one device id. Kavita uses device ids to pick which profile applies on a given device, so overlapping devices would make profile selection ambiguous; the guard enforces a one-device-to-one-user-profile invariant.
Source
Thrown at Kavita.Services/Reading/ReadingProfileService.cs:358
.ToListAsync();
foreach (var profile in profiles)
{
profile.DeviceIds.Remove(deviceId);
unitOfWork.AppUserReadingProfileRepository.Update(profile);
}
}
private static void DeviceOverlapGuard(List<AppUserReadingProfile> profiles)
{
var anyOverlap = profiles
.Any(rp => profiles
.Where(other => other.Id != rp.Id)
.Any(other => other.DeviceIds.Intersect(rp.DeviceIds).Any()));
if (anyOverlap)
{
throw new KavitaException("reading-profiles-device-overlap");
}
}
/// <summary>
/// Deletes all implicit profiles with overlapping ids (For devices 0 overlaps with 0). And removes links with
/// series & libraries
/// </summary>
/// <param name="profiles"></param>
/// <param name="seriesIds"></param>
/// <param name="libraryIds"></param>
/// <param name="deviceIds"></param>
private void DeleteImplicitAndRemoveFromUserProfiles(IList<AppUserReadingProfile> profiles, IList<int> seriesIds, IList<int> libraryIds, List<int>? deviceIds)
{
var implicitProfiles = profiles
.Where(DeviceIdFilter)
.Where(rp => rp.SeriesIds.Intersect(seriesIds).Any())
.Where(rp => rp.Kind == ReadingProfileKind.Implicit)
.ToList();View on GitHub (pinned to 9c3e540000)
Solutions
- De-duplicate device assignments across the profiles in the payload before submitting: each deviceId must appear in exactly one profile.
- If you intentionally moved a device, remove it from the previous profile's DeviceIds in the same request.
- Inspect the selectedProfiles passed to DeviceOverlapGuard and reconcile the conflicting DeviceIds lists.
- Add client-side validation that warns when a selected device is already bound to another profile.
Example fix
// before
DeviceOverlapGuard(selectedProfiles); // throws on first overlap
// after - reconcile before guarding
foreach (var p in selectedProfiles)
p.DeviceIds = p.DeviceIds.Distinct().Except(devicesClaimedByOthers(p, selectedProfiles)).ToList();
DeviceOverlapGuard(selectedProfiles); Defensive patterns
Strategy: validation
Validate before calling
static bool HasDeviceOverlap(IEnumerable<AppUserReadingProfile> profiles)
{
var list = profiles.ToList();
return list.Any(a => list.Any(b => b.Id != a.Id && b.DeviceIds.Intersect(a.DeviceIds).Any()));
}
// if (HasDeviceOverlap(selectedProfiles)) return BadRequest("..."); Type guard
static bool DeviceSetsAreUnique(IEnumerable<AppUserReadingProfile> profiles) => !HasDeviceOverlap(profiles);
Prevention
- Enforce one-device-per-user-profile in the UI before bulk save.
- When moving a device between profiles, remove it from the old one in the same batch.
- Unit-test DeviceOverlapGuard with overlapping inputs to assert the throw.
When it happens
Trigger: Bulk or multi-profile operations (e.g. updating several User profiles at once) where the payload assigns the same deviceId to two different profiles. Triggered whenever DeviceOverlapGuard detects profiles.Any(rp => profiles.Where(other => other.Id != rp.Id).Any(other => other.DeviceIds.Intersect(rp.DeviceIds).Any())).
Common situations: A device was reassigned to a new profile without being removed from the old one in the same batch; an import/sync job assigned overlapping device lists; or the UI allowed editing multiple profiles' device sets independently and they were committed together.
Related errors
- cant-assign-devices-to-default
- name-already-in-use
- cant-delete-default-profile
- profile-does-not-exist
- reading-list-name-exists
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/123cdc4591378e0e.
Report an issue: GitHub.