duplicati/duplicati · error · Exception
Failed to safe-delete volume {name}, blocks: {c}
Error message
Failed to safe-delete volume {name}, blocks: {c} What it means
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.
Source
Thrown at Duplicati/Library/Main/Database/Local/LocalBackupDatabase.cs:1847
/// <param name="token">The cancellation token to cancel the operation.</param>
/// <returns>A task that completes when the remote volume is safely deleted.</returns>
/// <exception cref="Exception">Thrown if the volume has associated blocks.</exception>
public async Task SafeDeleteRemoteVolumeAsync(string name, CancellationToken token)
{
var volumeid = await GetRemoteVolumeIDAsync(name, token).ConfigureAwait(false);
await using var cmd = m_connection.CreateCommand(m_rtr);
var c = await cmd.SetCommandAndParameters(@"
SELECT COUNT(*)
FROM ""Block""
WHERE ""VolumeID"" = @VolumeId
")
.SetParameterValue("@VolumeId", volumeid)
.ExecuteScalarInt64Async(-1, token)
.ConfigureAwait(false);
if (c != 0)
throw new Exception($"Failed to safe-delete volume {name}, blocks: {c}");
await RemoveRemoteVolumeAsync(name, token).ConfigureAwait(false);
}
/// <summary>
/// Retrieves the hashes of blocks that are on the blocklist for a given volume.
/// </summary>
/// <param name="name">The name of the volume to check.</param>
/// <param name="token"> The cancellation token to cancel the operation.</param>
/// <returns>A task that when awaited contains an array of blocklist hashes.</returns>
public async Task<string[]> GetBlocklistHashesAsync(string name, CancellationToken token)
{
var volumeid = GetRemoteVolumeIDAsync(name, token);
await using var cmd = m_connection.CreateCommand(m_rtr);
// Grab the strings and return as array to avoid concurrent access to the IEnumerable
cmd.SetCommandAndParameters(@"
SELECT DISTINCT ""Block"".""Hash""
FROM ""Block""View on GitHub (pinned to 3f348be3e3)
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.
Defensive patterns
Strategy: validation
Validate before calling
// Pre-check the safe-delete precondition:
long n = await CountBlocksOnVolumeAsync(volumeid, token);
if (n != 0)
throw new InvalidOperationException($"Volume {name} still has {n} blocks; relocate them first."); Try / catch
try { await db.SafeDeleteRemoteVolumeAsync(name, token); }
catch (Exception ex) when (ex.Message.Contains("Failed to safe-delete volume"))
{ Log.Warn($"Volume {name} still has blocks; run compaction/reclaim before deleting."); /* do not retry unchanged */ } Prevention
- 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.
When it happens
Trigger: Call SafeDeleteRemoteVolumeAsync(name) for a volume that still has >=1 Block row referencing it (COUNT(*) WHERE VolumeID = @VolumeId != 0).
Common situations: 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.
Related errors
- Hash is null
- Failed to move block {key} with size {size}, result count: {
- Failed to insert block {key} with size {size}, result count:
- Failed to insert blocklist hash {bh} for blockset {blockseti
- Could not find any blocks with the given hash: {h}
AI-assisted analysis of duplicati/duplicati@3f348be3e3 (2026-08-13).
Data as JSON: /api/errors/ae0e1f50f106343e.
Report an issue: GitHub.