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
- Close Explorer windows, shells, and any process whose working directory is inside the package folder, then retry.
- 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.
- 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.
- Re-check Directory.GetFiles/GetDirectories right before Directory.Delete and delete any stragglers (files recreated by a still-running process).
- 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
- Never keep a shell or Explorer window open inside a package directory you plan to delete.
- Terminate the package process and dispose its Process object before deleting, allowing handles to be released.
- Use generous retry backoff (seconds, not milliseconds) for directory deletion on Windows.
- Re-enumerate the directory immediately before the final Directory.Delete to catch recreated files.
- Store packages in a local, non-synced, writable location with correct ACLs.
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
- Failed to delete file
- Failed to delete junction point
- Source directory not found
- Source file does not exist
- Directory not found
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)