{"record":{"id":"07b2abff508a3c70","repo":"OrchardCMS/OrchardCore","slug":"cannot-delete-root-directory","errorCode":null,"errorMessage":"Cannot delete root directory.","messagePattern":"Cannot delete root directory\\.","errorType":"exception","errorClass":"FileStoreException","httpStatus":null,"severity":"error","filePath":"src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsFileStorage.cs","lineNumber":163,"sourceCode":"            });\n\n            return response.IsDeleteSuccessful();\n        }\n        catch (AmazonS3Exception ex) when (ex.StatusCode == HttpStatusCode.NotFound)\n        {\n            return false;\n        }\n        catch (AmazonS3Exception ex)\n        {\n            throw new FileStoreException($\"Error deleting file '{path}': {ex.Message}\", ex);\n        }\n    }\n\n    public async Task<bool> TryDeleteDirectoryAsync(string path)\n    {\n        if (string.IsNullOrWhiteSpace(path))\n        {\n            throw new FileStoreException(\"Cannot delete root directory.\");\n        }\n\n        var listObjectsResponse = await _amazonS3Client.ListObjectsV2Async(new ListObjectsV2Request\n        {\n            BucketName = _options.BucketName,\n            Prefix = NormalizePrefix(this.Combine(_basePrefix, path)),\n        });\n\n        if (listObjectsResponse.S3Objects?.Count > 0)\n        {\n            var deleteObjectsRequest = new DeleteObjectsRequest\n            {\n                BucketName = _options.BucketName,\n                Objects = listObjectsResponse.S3Objects\n                    .Select(metadata => new KeyVersion { Key = metadata.Key }).ToList(),\n            };\n\n            var response = await _amazonS3Client.DeleteObjectsAsync(deleteObjectsRequest);","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/OrchardCMS/OrchardCore/blob/4306c0717fe573f6fca1b4955909ddab6a192807/src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsFileStorage.cs#L145-L181","documentation":"AwsFileStorage refuses to delete the store's root: TryDeleteDirectoryAsync requires a non-empty, non-whitespace path because deleting it would wipe every object under the base prefix. This is a defensive guard against accidental whole-store deletion.","triggerScenarios":"Calling TryDeleteDirectoryAsync(null), TryDeleteDirectoryAsync(\"\") or a whitespace-only path; calling TryDeleteDirectoryAsync(\"/\") normalized to empty; content code that computes an empty directory path (e.g. deleting a container whose path resolved to root).","commonSituations":"Path-trimming bugs where splitting a full path yields an empty directory segment; recursively deleting parent folders until root is reached; admin bulk-delete operations on top-level media folders with empty path values.","solutions":["Guard call sites: skip deletion when the computed path is null/empty/whitespace","Trim leading slashes and re-check before invoking TryDeleteDirectoryAsync","Ensure media folder deletion logic never targets the root container — special-case it to clear contents instead","Fix path parsing so it cannot collapse a real folder path to empty"],"exampleFix":"// before\nawait fileStore.TryDeleteDirectoryAsync(folder.Path); // Path may be \"\"\n// after\nif (!string.IsNullOrWhiteSpace(folder.Path))\n{\n    await fileStore.TryDeleteDirectoryAsync(folder.Path.TrimStart('/'));\n}","handlingStrategy":"validation","validationCode":"if (string.IsNullOrWhiteSpace(path) || path.Trim('/', ' ') is \"\")\n    return; // refuse to delete root — never call TryDeleteDirectoryAsync with an empty path","typeGuard":"bool IsRootPath(string p) => string.IsNullOrWhiteSpace(p) || p.Trim('/', ' ').Length == 0;","tryCatchPattern":"try { await fileStore.TryDeleteDirectoryAsync(path); } catch (FileStoreException ex) when (ex.Message == \"Cannot delete root directory.\") { _logger.LogWarning(\"Refused root deletion attempt for {Path}\", path); }","preventionTips":["Always compute and validate the directory path before recursive deletion","Special-case the root container: clear contents instead of deleting it","Guard against path parsing that collapses folders to empty strings","Trim leading slashes and reject empty results before calling the API"],"tags":["aws","s3","file-storage","guard","root-directory"],"backgroundTag":"empty-required-field","analyzedSha":"4306c0717fe573f6fca1b4955909ddab6a192807","analyzedAt":"2026-09-13T17:41:05.024Z","contentChangedAt":"2026-09-13T17:41:05.024Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}