{"record":{"id":"e2d69205a5eac3dc","repo":"Kareadita/Kavita","slug":"collection-doesnt-exist","errorCode":null,"errorMessage":"collection-doesnt-exist","messagePattern":"collection-doesnt-exist","errorType":"exception","errorClass":"KavitaException","httpStatus":null,"severity":"error","filePath":"Kavita.Services/CollectionTagService.cs","lineNumber":37,"sourceCode":"public class CollectionTagService(IUnitOfWork unitOfWork, IEventHub eventHub, IDirectoryService directoryService) : ICollectionTagService\n{\n    public async Task<bool> DeleteTag(int tagId, AppUser user, CancellationToken ct = default)\n    {\n        var collectionTag = await unitOfWork.CollectionTagRepository.GetCollectionAsync(tagId, ct: ct);\n        if (collectionTag == null) return true;\n\n        user.Collections.Remove(collectionTag);\n\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))","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/CollectionTagService.cs#L19-L55","documentation":"Thrown by UpdateTag when the collection tag with the given dto.Id does not exist in the database. GetCollectionAsync(dto.Id) returns null, indicating the collection was deleted or the ID is invalid. It is the first validation in the update flow, checked before ownership and title validation.","triggerScenarios":"User edits a collection that was deleted in another session; the collection ID in the request is from a different user; a race condition where the collection is removed between the client loading the edit form and submitting changes.","commonSituations":"Multiple browser tabs or users managing collections concurrently; stale UI after collections were bulk-deleted; bookmarked URL pointing to a removed collection.","solutions":["Refresh the collections list in the UI and confirm the collection still exists before editing","Verify the collection ID matches one returned by the user's collections endpoint","Handle the KavitaException gracefully in the controller and return a 404 to the client"],"exampleFix":"// Guard in the calling controller:\n// var collection = await collectionService.GetCollection(dto.Id, userId);\n// if (collection == null) return NotFound();","handlingStrategy":"try-catch","validationCode":"// Verify collection exists before updating:\n// var collection = await unitOfWork.CollectionTagRepository.GetCollectionAsync(dto.Id, ct: ct);\n// if (collection == null) return NotFound(\"Collection not found\");\n// if (collection.AppUserId != userId) return Forbid();","typeGuard":null,"tryCatchPattern":"// try {\n//     await collectionTagService.UpdateTag(dto, userId, ct);\n//     return Ok();\n// } catch (KavitaException ex) {\n//     return ex.Message switch {\n//         \"collection-doesnt-exist\" => NotFound(),\n//         \"access-denied\" => Forbid(),\n//         _ => BadRequest(ex.Message)\n//     };\n// }","preventionTips":["Refresh collections list before editing to avoid stale IDs","Handle concurrent deletion scenarios with graceful 404 responses","Consider optimistic concurrency tokens for collection edits"],"tags":["collection","not-found","update"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}