{"record":{"id":"1b369d1a03fd3f67","repo":"Kareadita/Kavita","slug":"collection-tag-duplicate","errorCode":null,"errorMessage":"collection-tag-duplicate","messagePattern":"collection-tag-duplicate","errorType":"exception","errorClass":"KavitaException","httpStatus":null,"severity":"warning","filePath":"Kavita.Services/CollectionTagService.cs","lineNumber":45,"sourceCode":"\n        if (!unitOfWork.HasChanges()) return true;\n\n        return await unitOfWork.CommitAsync(ct);\n    }\n\n\n    public async Task<bool> UpdateTag(AppUserCollectionDto dto, int userId, CancellationToken ct = default)\n    {\n        var existingTag = await unitOfWork.CollectionTagRepository.GetCollectionAsync(dto.Id, ct: ct);\n        if (existingTag == null) throw new KavitaException(\"collection-doesnt-exist\");\n        if (existingTag.AppUserId != userId) throw new KavitaException(\"access-denied\");\n\n        var title = dto.Title.Trim();\n        if (string.IsNullOrEmpty(title)) throw new KavitaException(\"collection-tag-title-required\");\n\n        // Ensure the title doesn't exist on the user's account already\n        if (!title.Equals(existingTag.Title) && await unitOfWork.CollectionTagRepository.CollectionExists(dto.Title, userId, ct))\n            throw new KavitaException(\"collection-tag-duplicate\");\n\n        existingTag.Items ??= [];\n        if (existingTag.Source == ScrobbleProvider.Kavita)\n        {\n            existingTag.Title = title;\n            existingTag.NormalizedTitle = dto.Title.ToNormalized();\n        }\n\n        var roles = await unitOfWork.UserRepository.GetRoles(userId, ct);\n        if (roles.Contains(PolicyConstants.AdminRole) || roles.Contains(PolicyConstants.PromoteRole))\n        {\n            existingTag.Promoted = dto.Promoted;\n        }\n        existingTag.CoverImageLocked = dto.CoverImageLocked;\n        unitOfWork.CollectionTagRepository.Update(existingTag);\n\n        // Check if Tag has updated (Summary)\n        var summary = (dto.Summary ?? string.Empty).Trim();","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/CollectionTagService.cs#L27-L63","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Choose a unique collection name that does not match any existing collection for the user","Delete or rename the conflicting collection first, then apply the desired name","Implement client-side real-time duplicate checking against the user's existing collection names before submission"],"exampleFix":"// Client-side check before submit:\n// const isDuplicate = collections.value.some(\n//   c => c.id !== dto.id && c.title.toLowerCase() === dto.title.trim().toLowerCase()\n// );\n// if (isDuplicate) { toast.error('A collection with this name already exists'); return; }","handlingStrategy":"validation","validationCode":"// Check for duplicates before calling UpdateTag:\n// var trimmedTitle = dto.Title.Trim();\n// if (!trimmedTitle.Equals(existingTitle) &&\n//     await unitOfWork.CollectionTagRepository.CollectionExists(trimmedTitle, userId, ct)) {\n//     return BadRequest(\"A collection with this name already exists\");\n// }","typeGuard":null,"tryCatchPattern":"// try {\n//     await collectionTagService.UpdateTag(dto, userId, ct);\n// } catch (KavitaException ex) when (ex.Message == \"collection-tag-duplicate\") {\n//     return BadRequest(new { error = \"You already have a collection with this name.\" });\n// }","preventionTips":["Fetch the user's existing collection names and check for duplicates on the client before form submission","Use debounced real-time validation as the user types the new title","Normalize titles (trim, lowercase) for comparison to catch case-only duplicates"],"tags":["collection","duplicate","validation","unique-constraint"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}