{"record":{"id":"4f96ef1d664e2406","repo":"LykosAI/StabilityMatrix","slug":"failed-to-delete-directory-targetdirectory","errorCode":null,"errorMessage":"Failed to delete directory {targetDirectory}","messagePattern":"Failed to delete directory (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"StabilityMatrix/ViewModels/PackageManagerViewModel.cs","lineNumber":276,"sourceCode":"        {\n            try\n            {\n                File.SetAttributes(filePath, FileAttributes.Normal);\n                File.Delete(filePath);\n            }\n            catch (IOException ex)\n            {\n                throw new IOException($\"Failed to delete file {filePath}\", ex);\n            }\n        }\n        // Delete the target directory itself\n        try\n        {\n            Directory.Delete(targetDirectory, false);\n        }\n        catch (IOException ex)\n        {\n            throw new IOException($\"Failed to delete directory {targetDirectory}\", ex);\n        }\n    }\n\n    private async Task UpdateSelectedPackage()\n    {\n        var package = packageFactory.FindPackageByName(SelectedPackage?.PackageName ?? string.Empty);\n        if (package == null)\n        {\n            logger.LogError($\"Could not find package {SelectedPackage.PackageName}\");\n            return;\n        }\n\n        ProgressText = $\"Updating {SelectedPackage.DisplayName} to latest version...\";\n        package.InstallLocation = SelectedPackage.FullPath!;\n        var progress = new Progress<ProgressReport>(progress =>\n        {\n            var percent = Convert.ToInt32(progress.Percentage);\n            if (progress.IsIndeterminate || progress.Progress == -1)","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/LykosAI/StabilityMatrix/blob/af93d6ef57c01cd890d7e0ad0a9ea8c9fcda3002/StabilityMatrix/ViewModels/PackageManagerViewModel.cs#L258-L294","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ntry\n{\n    Directory.Delete(targetDirectory, false);\n}\ncatch (IOException ex)\n{\n    throw new IOException($\"Failed to delete directory {targetDirectory}\", ex);\n}\n// after\ntry\n{\n    // clear any stragglers recreated between enumeration and delete\n    foreach (var f in Directory.GetFiles(targetDirectory)) File.Delete(f);\n    foreach (var d in Directory.GetDirectories(targetDirectory)) Directory.Delete(d, true);\n    Directory.Delete(targetDirectory, false);\n}\ncatch (IOException ex) when (ex.HResult == unchecked((int)0x80070091))\n{\n    // directory not empty: retry once more after a short delay\n    await Task.Delay(500);\n    Directory.Delete(targetDirectory, true);\n}","handlingStrategy":"retry","validationCode":"if (!Directory.Exists(packagePath)) return;\n// pre-check: no process working directory inside the tree (best effort via open-handle probe)\nforeach (var d in Directory.EnumerateDirectories(packagePath, \"*\", SearchOption.AllDirectories))\n{\n    try { Directory.Move(d, d); } // throws if handle held / locked\n    catch (IOException) { throw new InvalidOperationException($\"Directory is in use: {d}\"); }\n}","typeGuard":"static bool IsEmptyDirectory(string path) =>\n    Directory.Exists(path) && !Directory.EnumerateFileSystemEntries(path).Any();","tryCatchPattern":"try\n{\n    await DeleteDirectoryAsync(packagePath);\n}\ncatch (IOException ex)\n{\n    if (ex.InnerException?.HResult == unchecked((int)0x80070091))\n        NotifyUser(\"Folder is not empty — something recreated a file. Close apps and retry.\");\n    else\n        logger.LogError(ex, \"Directory delete failed for {Path}\", packagePath);\n}","preventionTips":["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."],"tags":["file-io","windows","directory-delete","ioexception"],"backgroundTag":"file-delete-failed","analyzedSha":"af93d6ef57c01cd890d7e0ad0a9ea8c9fcda3002","analyzedAt":"2026-09-12T19:02:43.389Z","contentChangedAt":"2026-09-12T19:02:43.389Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}