{"record":{"id":"ae0e1f50f106343e","repo":"duplicati/duplicati","slug":"failed-to-safe-delete-volume-name-blocks-c","errorCode":null,"errorMessage":"Failed to safe-delete volume {name}, blocks: {c}","messagePattern":"Failed to safe-delete volume (.+?), blocks: (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"Duplicati/Library/Main/Database/Local/LocalBackupDatabase.cs","lineNumber":1847,"sourceCode":"        /// <param name=\"token\">The cancellation token to cancel the operation.</param>\n        /// <returns>A task that completes when the remote volume is safely deleted.</returns>\n        /// <exception cref=\"Exception\">Thrown if the volume has associated blocks.</exception>\n        public async Task SafeDeleteRemoteVolumeAsync(string name, CancellationToken token)\n        {\n            var volumeid = await GetRemoteVolumeIDAsync(name, token).ConfigureAwait(false);\n\n            await using var cmd = m_connection.CreateCommand(m_rtr);\n            var c = await cmd.SetCommandAndParameters(@\"\n                    SELECT COUNT(*)\n                    FROM \"\"Block\"\"\n                    WHERE \"\"VolumeID\"\" = @VolumeId\n                \")\n                .SetParameterValue(\"@VolumeId\", volumeid)\n                .ExecuteScalarInt64Async(-1, token)\n                .ConfigureAwait(false);\n\n            if (c != 0)\n                throw new Exception($\"Failed to safe-delete volume {name}, blocks: {c}\");\n\n            await RemoveRemoteVolumeAsync(name, token).ConfigureAwait(false);\n        }\n\n        /// <summary>\n        /// Retrieves the hashes of blocks that are on the blocklist for a given volume.\n        /// </summary>\n        /// <param name=\"name\">The name of the volume to check.</param>\n        /// <param name=\"token\"> The cancellation token to cancel the operation.</param>\n        /// <returns>A task that when awaited contains an array of blocklist hashes.</returns>\n        public async Task<string[]> GetBlocklistHashesAsync(string name, CancellationToken token)\n        {\n            var volumeid = GetRemoteVolumeIDAsync(name, token);\n            await using var cmd = m_connection.CreateCommand(m_rtr);\n            // Grab the strings and return as array to avoid concurrent access to the IEnumerable\n            cmd.SetCommandAndParameters(@\"\n                    SELECT DISTINCT \"\"Block\"\".\"\"Hash\"\"\n                    FROM \"\"Block\"\"","sourceCodeStart":1829,"sourceCodeEnd":1865,"githubUrl":"https://github.com/duplicati/duplicati/blob/3f348be3e33f5d72d414e3ad55839c2ba34dda67/Duplicati/Library/Main/Database/Local/LocalBackupDatabase.cs#L1829-L1865","documentation":"Generic Exception in SafeDeleteRemoteVolumeAsync: it COUNTs Block rows referencing the volume and refuses to delete if any exist (c != 0). The method is intentionally a guard — it only deletes a volume proven to have zero blocks, preventing orphaned block references. Hitting it means the caller tried to delete a volume that still holds block data.","triggerScenarios":"Call SafeDeleteRemoteVolumeAsync(name) for a volume that still has >=1 Block row referencing it (COUNT(*) WHERE VolumeID = @VolumeId != 0).","commonSituations":"Calling safe-delete during/after a failed block relocation that left blocks on the volume; race where a backup added blocks to the volume between the empty-check and delete; caller logic deleting the wrong volume; leftover blocks after a partial purge.","solutions":["Move/remove all blocks off the volume (compaction/reclaim) before safe-deleting it.","Re-run the block-volume cleanup so no blocks reference the target volume.","Serialize operations so no backup adds blocks to the volume mid-delete.","Run database repair to reconcile Block/Volume references."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Pre-check the safe-delete precondition:\nlong n = await CountBlocksOnVolumeAsync(volumeid, token);\nif (n != 0)\n    throw new InvalidOperationException($\"Volume {name} still has {n} blocks; relocate them first.\");","typeGuard":null,"tryCatchPattern":"try { await db.SafeDeleteRemoteVolumeAsync(name, token); }\ncatch (Exception ex) when (ex.Message.Contains(\"Failed to safe-delete volume\"))\n{ Log.Warn($\"Volume {name} still has blocks; run compaction/reclaim before deleting.\"); /* do not retry unchanged */ }","preventionTips":["Complete block relocation/compaction before deleting a volume.","Treat this throw as a legitimate guard, not a transient retry.","Single-writer discipline to avoid races adding blocks mid-delete."],"tags":["database","sqlite","data-integrity","backup"],"backgroundTag":null,"analyzedSha":"3f348be3e33f5d72d414e3ad55839c2ba34dda67","analyzedAt":"2026-08-13T16:48:27.008Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}