{"record":{"id":"b4d6f9181158180a","repo":"Kareadita/Kavita","slug":"kavita-found-corrupted-volume-entries-on-series-n","errorCode":null,"errorMessage":"Kavita found corrupted volume entries on {series.Name}. Please delete the series from Kavita via UI and rescan","messagePattern":"Kavita found corrupted volume entries on (.+?)\\. Please delete the series from Kavita via UI and rescan","errorType":"exception","errorClass":"KavitaException","httpStatus":null,"severity":"critical","filePath":"Kavita.Services/Scanner/ProcessSeries.cs","lineNumber":684,"sourceCode":"\n    private async Task UpdateVolumes(Dictionary<string, Person> databasePeople, MetadataSettingsDto settings, Series series, IList<ParserInfo> parsedInfos, bool forceUpdate = false)\n    {\n        // Add new volumes and update chapters per volume\n        var distinctVolumes = parsedInfos.DistinctVolumes();\n        foreach (var volumeNumber in distinctVolumes)\n        {\n            Volume? volume;\n            try\n            {\n                // With the Name change to be formatted, Name no longer working because Name returns \"1\" and volumeNumber is \"1.0\", so we use LookupName as the original\n                volume = series.Volumes.SingleOrDefault(s => s.LookupName == volumeNumber);\n            }\n            catch (Exception ex)\n            {\n                // TODO: Push this to UI in some way\n                if (!ex.Message.Equals(\"Sequence contains more than one matching element\")) throw;\n                logger.LogCritical(ex, \"[ScannerService] Kavita found corrupted volume entries on {SeriesName}. Please delete the series from Kavita via UI and rescan\", series.Name);\n                throw new KavitaException(\n                    $\"Kavita found corrupted volume entries on {series.Name}. Please delete the series from Kavita via UI and rescan\");\n            }\n            if (volume == null)\n            {\n                volume = new VolumeBuilder(volumeNumber)\n                    .WithSeriesId(series.Id)\n                    .Build();\n                series.Volumes.Add(volume);\n            }\n\n            volume.LookupName = volumeNumber;\n            volume.Name = volume.GetNumberTitle();\n\n            var infos = parsedInfos.Where(p => p.Volumes == volumeNumber).ToArray();\n\n            await UpdateChapters(new UpdateChapterArgs\n            {\n                Settings = settings,","sourceCodeStart":666,"sourceCodeEnd":702,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/Scanner/ProcessSeries.cs#L666-L702","documentation":"Thrown by ProcessSeries.UpdateVolumes when series.Volumes.SingleOrDefault(LookupName == volumeNumber) throws 'Sequence contains more than one matching element' - i.e. the database has two or more Volume rows for the same series with the same LookupName. This is data corruption: the scanner cannot decide which volume to update, so it aborts the scan for that series and asks the user to delete and rescan.","triggerScenarios":"During a library scan, UpdateVolumes iterates distinct parsed volume numbers and the SingleOrDefault lookup matches multiple Volume entities sharing the same LookupName for one series - usually from a prior partial scan, a crashed migration, or a manual DB edit.","commonSituations":"An interrupted scan left duplicate volume rows; a migration changed LookupName semantics and created dupes; files were renamed so two folders now parse to the same volume number but old rows weren't cleaned; or a manual SQLite edit inserted a duplicate.","solutions":["Delete the affected series from the Kavita UI and rescan so the volumes are rebuilt cleanly (the message's prescribed fix).","Inspect the Volumes table for the seriesId and remove duplicate rows with the same LookupName, keeping one.","Ensure file/folder naming is consistent so two distinct folders don't parse to the same volume number.","Run the scan to completion without interruption; avoid killing Kavita mid-scan."],"exampleFix":"// before - data corruption causes the throw mid-scan\nvolume = series.Volumes.SingleOrDefault(s => s.LookupName == volumeNumber);\n\n// after - heal duplicates before scanning\nvar dups = series.Volumes.Where(v => v.LookupName == volumeNumber).ToList();\nif (dups.Count > 1)\n{\n    // merge chapters into the first, remove the rest, then re-scan\n    var keep = dups[0];\n    foreach (var extra in dups.Skip(1)) { keep.Chapters.AddRange(extra.Chapters); series.Volumes.Remove(extra); }\n}","handlingStrategy":"validation","validationCode":"var dups = series.Volumes.GroupBy(v => v.LookupName).Where(g => g.Count() > 1).ToList();\nif (dups.Any()) { /* heal or abort before scanning */ return BadRequest(\"Duplicate volumes detected, rescan needed\"); }","typeGuard":"static bool VolumesAreUnique(Series s) => s.Volumes.GroupBy(v => v.LookupName).All(g => g.Count() == 1);","tryCatchPattern":"try { await scannerService.ScanSeries(seriesId); }\ncatch (KavitaException ex) when (ex.Message.Contains(\"corrupted volume entries\"))\n{ await adminService.DeleteAndRescan(seriesId); }","preventionTips":["Avoid interrupting scans mid-way.","Keep folder/file naming consistent so two folders don't parse to the same volume.","Periodically audit for duplicate Volume rows by LookupName.","Don't hand-edit the Volumes table."],"tags":["scanner","data-corruption","volumes","data-integrity"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}