OrchardCMS/OrchardCore · error · FileStoreException
Cannot delete root directory.
Error message
Cannot delete root directory.
What it means
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.
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
Example fix
// before
await fileStore.TryDeleteDirectoryAsync(folder.Path); // Path may be ""
// after
if (!string.IsNullOrWhiteSpace(folder.Path))
{
await fileStore.TryDeleteDirectoryAsync(folder.Path.TrimStart('/'));
} Defensive patterns
Strategy: validation
Validate before calling
if (string.IsNullOrWhiteSpace(path) || path.Trim('/', ' ') is "")
return; // refuse to delete root — never call TryDeleteDirectoryAsync with an empty path Type guard
bool IsRootPath(string p) => string.IsNullOrWhiteSpace(p) || p.Trim('/', ' ').Length == 0; Try / catch
try { await fileStore.TryDeleteDirectoryAsync(path); } catch (FileStoreException ex) when (ex.Message == "Cannot delete root directory.") { _logger.LogWarning("Refused root deletion attempt for {Path}", path); } Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- Error retrieving file info for
- Error creating directory
- Error deleting file
- Cannot create file ' ', S3 service threw an exception
- The values for and must not be the same.
AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13).
Data as JSON: /api/errors/07b2abff508a3c70.
Report an issue: GitHub.
Appendix: source
Thrown at src/OrchardCore/OrchardCore.FileStorage.AmazonS3/AwsFileStorage.cs:163
});
return response.IsDeleteSuccessful();
}
catch (AmazonS3Exception ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
return false;
}
catch (AmazonS3Exception ex)
{
throw new FileStoreException($"Error deleting file '{path}': {ex.Message}", ex);
}
}
public async Task<bool> TryDeleteDirectoryAsync(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
throw new FileStoreException("Cannot delete root directory.");
}
var listObjectsResponse = await _amazonS3Client.ListObjectsV2Async(new ListObjectsV2Request
{
BucketName = _options.BucketName,
Prefix = NormalizePrefix(this.Combine(_basePrefix, path)),
});
if (listObjectsResponse.S3Objects?.Count > 0)
{
var deleteObjectsRequest = new DeleteObjectsRequest
{
BucketName = _options.BucketName,
Objects = listObjectsResponse.S3Objects
.Select(metadata => new KeyVersion { Key = metadata.Key }).ToList(),
};
var response = await _amazonS3Client.DeleteObjectsAsync(deleteObjectsRequest);View on GitHub (pinned to 4306c0717f)