LykosAI/StabilityMatrix · error · IOException

Failed to delete directory

Error message

Failed to delete directory {targetDirectory}

What it means

This IOException is thrown by PackageManagerViewModel.DeleteDirectory when the final Directory.Delete(targetDirectory, false) — removing the (now emptied) directory itself — fails. All subdirectories and files were processed recursively first, so the directory should be empty; failure means the OS still refuses to remove the directory entry. Like the file-level case, DeleteDirectoryAsync retries it up to 3 times because directory handles are frequently released with a short delay.

Solutions

  1. Close Explorer windows, shells, and any process whose working directory is inside the package folder, then retry.
  2. Inspect the inner exception HResult: 0x80070091 (directory not empty) means a file was recreated or remains — re-run the delete; 0x80070005 means permissions; 0x80070020 a handle still open.
  3. Increase Polly retry count/backoff (e.g. 5 attempts, seconds-scale) since Windows can take a moment to release directory handles after killing a process.
  4. Re-check Directory.GetFiles/GetDirectories right before Directory.Delete and delete any stragglers (files recreated by a still-running process).
  5. Verify drive is writable and ACLs allow deletion; move packages out of cloud-synced/special folders if the problem persists.

Example fix

// before
try
{
    Directory.Delete(targetDirectory, false);
}
catch (IOException ex)
{
    throw new IOException($"Failed to delete directory {targetDirectory}", ex);
}
// after
try
{
    // clear any stragglers recreated between enumeration and delete
    foreach (var f in Directory.GetFiles(targetDirectory)) File.Delete(f);
    foreach (var d in Directory.GetDirectories(targetDirectory)) Directory.Delete(d, true);
    Directory.Delete(targetDirectory, false);
}
catch (IOException ex) when (ex.HResult == unchecked((int)0x80070091))
{
    // directory not empty: retry once more after a short delay
    await Task.Delay(500);
    Directory.Delete(targetDirectory, true);
}
Defensive patterns

Strategy: retry

Validate before calling

if (!Directory.Exists(packagePath)) return;
// pre-check: no process working directory inside the tree (best effort via open-handle probe)
foreach (var d in Directory.EnumerateDirectories(packagePath, "*", SearchOption.AllDirectories))
{
    try { Directory.Move(d, d); } // throws if handle held / locked
    catch (IOException) { throw new InvalidOperationException($"Directory is in use: {d}"); }
}

Type guard

static bool IsEmptyDirectory(string path) =>
    Directory.Exists(path) && !Directory.EnumerateFileSystemEntries(path).Any();

Try / catch

try
{
    await DeleteDirectoryAsync(packagePath);
}
catch (IOException ex)
{
    if (ex.InnerException?.HResult == unchecked((int)0x80070091))
        NotifyUser("Folder is not empty — something recreated a file. Close apps and retry.");
    else
        logger.LogError(ex, "Directory delete failed for {Path}", packagePath);
}

Prevention

When it happens

Trigger: Directory.Delete(targetDirectory, false) throws IOException after contents were removed: some process's current working directory is the target (or a deleted child), a handle to the directory is still open (Explorer window, terminal cd'd into it), a leftover hidden/system file or a newly created file remains, the directory is a mount/reparse point not caught by the LinkTarget check, or read-only media/permissions block removal.

Common situations: A terminal or Explorer window is sitting inside the package folder being deleted; the package process was just killed and Windows has not yet released its directory handle (retry backoff too short); an application regenerated a log/cache file between GetFiles and Delete; the package sits on a network share or cloud-synced folder.

Related errors


AI-assisted analysis of LykosAI/StabilityMatrix@af93d6ef57 (2026-09-12). Data as JSON: /api/errors/4f96ef1d664e2406. Report an issue: GitHub.

Appendix: source

Thrown at StabilityMatrix/ViewModels/PackageManagerViewModel.cs:276

        {
            try
            {
                File.SetAttributes(filePath, FileAttributes.Normal);
                File.Delete(filePath);
            }
            catch (IOException ex)
            {
                throw new IOException($"Failed to delete file {filePath}", ex);
            }
        }
        // Delete the target directory itself
        try
        {
            Directory.Delete(targetDirectory, false);
        }
        catch (IOException ex)
        {
            throw new IOException($"Failed to delete directory {targetDirectory}", ex);
        }
    }

    private async Task UpdateSelectedPackage()
    {
        var package = packageFactory.FindPackageByName(SelectedPackage?.PackageName ?? string.Empty);
        if (package == null)
        {
            logger.LogError($"Could not find package {SelectedPackage.PackageName}");
            return;
        }

        ProgressText = $"Updating {SelectedPackage.DisplayName} to latest version...";
        package.InstallLocation = SelectedPackage.FullPath!;
        var progress = new Progress<ProgressReport>(progress =>
        {
            var percent = Convert.ToInt32(progress.Percentage);
            if (progress.IsIndeterminate || progress.Progress == -1)

View on GitHub (pinned to af93d6ef57)