Kareadita/Kavita · error · KavitaException
cant-assign-devices-to-default
Error message
cant-assign-devices-to-default
What it means
Thrown by ReadingProfileService.SetProfileDevices when the target profile's Kind is ReadingProfileKind.Default. The Default profile is the system-managed fallback profile that every user inherits; it is intentionally not allowed to carry device bindings because it must match all unscoped reading. Assigning devices to it would create an ambiguous device-scoped rule on a profile that is meant to be device-agnostic.
Source
Thrown at Kavita.Services/Reading/ReadingProfileService.cs:306
.ProjectTo<UserReadingProfileDto>(mapper.ConfigurationProvider)
.ToListAsync();
}
public Task<List<UserReadingProfileDto>> GetReadingProfileDtosForSeries(int userId, int seriesId)
{
return unitOfWork.DataContext.AppUserReadingProfiles
.Where(rp => rp.AppUserId == userId && rp.SeriesIds.Contains(seriesId))
.Where(rp => rp.Kind == ReadingProfileKind.User)
.ProjectTo<UserReadingProfileDto>(mapper.ConfigurationProvider)
.ToListAsync();
}
public async Task SetProfileDevices(int userId, int profileId, List<int> deviceIds)
{
var profile = await unitOfWork.AppUserReadingProfileRepository.GetUserProfile(userId, profileId);
if (profile == null) throw new KavitaException("profile-doesnt-exist");
if (profile.Kind == ReadingProfileKind.Default) throw new KavitaException("cant-assign-devices-to-default");
profile.DeviceIds = deviceIds;
unitOfWork.AppUserReadingProfileRepository.Update(profile);
await unitOfWork.CommitAsync();
// Remove series & library links from profiles where there is now overlap with devices
// E.g. for the same series there are now two profiles that would match
var profiles = await unitOfWork.AppUserReadingProfileRepository.GetProfilesForUser(userId);
var overlappingProfiles = profiles
.Where(rp => rp.Id != profileId)
.Where(rp => rp.Kind == ReadingProfileKind.User)
.Where(rp => (rp.DeviceIds.Count == 0 && deviceIds.Count == 0)
|| rp.DeviceIds.Intersect(deviceIds).Any())
.Where(rp => rp.SeriesIds.Intersect(profile.SeriesIds).Any()
|| rp.LibraryIds.Intersect(profile.LibraryIds).Any());
View on GitHub (pinned to 9c3e540000)
Solutions
- Filter the profile selector in the UI to only profiles where Kind == User before invoking SetProfileDevices.
- Guard the API call by fetching the profile's Kind first and short-circuiting when it is Default.
- If devices truly must be scoped, create a new User reading profile and assign devices there instead of reusing the Default profile.
- Reproduce with the user's actual profile list to confirm which profileId was passed and correct the caller.
Example fix
// before
await readingProfileService.SetProfileDevices(userId, selectedProfileId, deviceIds);
// after
var profile = await unitOfWork.AppUserReadingProfileRepository.GetUserProfile(userId, selectedProfileId);
if (profile?.Kind == ReadingProfileKind.Default)
{
// surface 'cant-assign-devices-to-default' to the user as a UI hint, do not call the service
return BadRequest("Devices cannot be attached to the Default profile");
}
await readingProfileService.SetProfileDevices(userId, selectedProfileId, deviceIds); Defensive patterns
Strategy: validation
Validate before calling
var profile = await unitOfWork.AppUserReadingProfileRepository.GetUserProfile(userId, profileId);
if (profile is null) return BadRequest("profile-doesnt-exist");
if (profile.Kind == ReadingProfileKind.Default)
return BadRequest("Devices cannot be attached to the Default profile");
// safe to call SetProfileDevices Type guard
static bool CanAssignDevices(AppUserReadingProfile p) => p is not null && p.Kind != ReadingProfileKind.Default;
Prevention
- Filter the profile picker UI to Kind == User profiles only.
- Never reuse the Default profile's id in device-assignment payloads.
- Log profile.Kind before calling SetProfileDevices during debugging.
When it happens
Trigger: Calling SetProfileDevices(userId, profileId, deviceIds) where profileId resolves to the user's Default profile (Kind == ReadingProfileKind.Default). This happens when the UI/API passes the Default profile's id instead of a User profile id, e.g. POSTing to the set-devices endpoint with the id of the auto-created default profile.
Common situations: Frontend lists all profiles in one dropdown and the user picks the Default one; a migration created a Default profile whose id leaked into a device-assignment payload; or a client hard-codes profileId=0 / first-profile-id which maps to Default.
Related errors
- reading-profiles-device-overlap
- 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/4f4bc1792c177a92.
Report an issue: GitHub.