Kareadita/Kavita · warning · KavitaException

collection-tag-duplicate

Error message

collection-tag-duplicate

What it means

Thrown by UpdateTag when the new title differs from the existing one AND another collection with that title already exists for the same user. The check uses CollectionExists(dto.Title, userId) which scopes uniqueness per user. This prevents duplicate collection names within a single user's library.

Source

Thrown at Kavita.Services/CollectionTagService.cs:45

        if (!unitOfWork.HasChanges()) return true;

        return await unitOfWork.CommitAsync(ct);
    }


    public async Task<bool> UpdateTag(AppUserCollectionDto dto, int userId, CancellationToken ct = default)
    {
        var existingTag = await unitOfWork.CollectionTagRepository.GetCollectionAsync(dto.Id, ct: ct);
        if (existingTag == null) throw new KavitaException("collection-doesnt-exist");
        if (existingTag.AppUserId != userId) throw new KavitaException("access-denied");

        var title = dto.Title.Trim();
        if (string.IsNullOrEmpty(title)) throw new KavitaException("collection-tag-title-required");

        // Ensure the title doesn't exist on the user's account already
        if (!title.Equals(existingTag.Title) && await unitOfWork.CollectionTagRepository.CollectionExists(dto.Title, userId, ct))
            throw new KavitaException("collection-tag-duplicate");

        existingTag.Items ??= [];
        if (existingTag.Source == ScrobbleProvider.Kavita)
        {
            existingTag.Title = title;
            existingTag.NormalizedTitle = dto.Title.ToNormalized();
        }

        var roles = await unitOfWork.UserRepository.GetRoles(userId, ct);
        if (roles.Contains(PolicyConstants.AdminRole) || roles.Contains(PolicyConstants.PromoteRole))
        {
            existingTag.Promoted = dto.Promoted;
        }
        existingTag.CoverImageLocked = dto.CoverImageLocked;
        unitOfWork.CollectionTagRepository.Update(existingTag);

        // Check if Tag has updated (Summary)
        var summary = (dto.Summary ?? string.Empty).Trim();

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Choose a unique collection name that does not match any existing collection for the user
  2. Delete or rename the conflicting collection first, then apply the desired name
  3. Implement client-side real-time duplicate checking against the user's existing collection names before submission

Example fix

// Client-side check before submit:
// const isDuplicate = collections.value.some(
//   c => c.id !== dto.id && c.title.toLowerCase() === dto.title.trim().toLowerCase()
// );
// if (isDuplicate) { toast.error('A collection with this name already exists'); return; }
Defensive patterns

Strategy: validation

Validate before calling

// Check for duplicates before calling UpdateTag:
// var trimmedTitle = dto.Title.Trim();
// if (!trimmedTitle.Equals(existingTitle) &&
//     await unitOfWork.CollectionTagRepository.CollectionExists(trimmedTitle, userId, ct)) {
//     return BadRequest("A collection with this name already exists");
// }

Try / catch

// try {
//     await collectionTagService.UpdateTag(dto, userId, ct);
// } catch (KavitaException ex) when (ex.Message == "collection-tag-duplicate") {
//     return BadRequest(new { error = "You already have a collection with this name." });
// }

Prevention

When it happens

Trigger: User renames a collection to match the name of another collection they own; two collections with similar names where one was auto-generated by a scrobble/aggregation service; user merges collections by renaming to an existing name.

Common situations: User has many collections and forgets they already used a particular name; collection names differ only by case or whitespace and the normalization check catches the collision; importing collections from a backup that creates duplicates.

Related errors


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