apache/hadoop · error · PathIOException

Cannot delete root path

Error message

Cannot delete root path

What it means

delete() on the bucket root throws PathIOException("Cannot delete root path") when a non-recursive delete is attempted on a non-empty root. This is deliberate: contract tests (AbstractContractRootDirectoryTest#testRmNonEmptyRootDirNonRecursive) require an exception here, while recursive delete of a non-empty root returns false (testRmRootRecursive) and an empty root returns true.

Source

Thrown at hadoop-cloud-storage-project/hadoop-tos/src/main/java/org/apache/hadoop/fs/tosfs/RawFileSystem.java:381

   * @return true if root directory is empty, false if trying to delete a non-empty dir recursively.
   * @throws IOException if trying to delete the non-empty root dir non-recursively.
   */
  private boolean deleteRoot(Path root, boolean recursive) throws IOException {
    LOG.info("Delete the {} root directory of {}", bucket, recursive);
    boolean isEmptyDir = fsOps.isEmptyDirectory(root);
    if (isEmptyDir) {
      return true;
    }
    if (recursive) {
      // AbstractContractRootDirectoryTest#testRmRootRecursive doesn't expect any exception if
      // trying to delete a non-empty root directory recursively, so we have to return false here
      // instead of throwing a IOException.
      return false;
    } else {
      // AbstractContractRootDirectoryTest#testRmNonEmptyRootDirNonRecursive expect a exception if
      // trying to delete a non-empty root directory non-recursively, so we have to throw a
      // IOException instead of returning false.
      throw new PathIOException(bucket, "Cannot delete root path");
    }
  }

  @Override
  public RawFileStatus[] listStatus(Path f) throws IOException {
    LOG.debug("List status for path: {}", f);
    return Iterators.toArray(listStatus(f, false), RawFileStatus.class);
  }

  public Iterator<RawFileStatus> listStatus(Path f, boolean recursive) throws IOException {
    Path path = makeQualified(f);
    // Assuming path is a dir at first.
    Iterator<RawFileStatus> iterator = fsOps.listDir(path, recursive, key -> true).iterator();
    if (iterator.hasNext()) {
      return iterator;
    } else {
      RawFileStatus fileStatus = innerFileStatus(path);
      if (fileStatus.isFile()) {

View on GitHub (pinned to 2add963021)

Solutions

  1. Never delete the root; iterate fs.listStatus(root) and delete children explicitly
  2. If using delete(f, false) for empty-dir semantics, guard with a root check and skip or log instead
  3. Catch PathIOException for this specific case when generic cleanup code must tolerate it

Example fix

// before
fs.delete(new Path("/"), false); // non-empty bucket root -> PathIOException

// after
Path root = fs.getHomeDirectory().getParent(); // bucket root
if (!f.isRoot()) { fs.delete(f, recursive); }
else { for (FileStatus st : fs.listStatus(f)) { fs.delete(st.getPath(), true); } }
Defensive patterns

Strategy: try-catch

Validate before calling

Path q = f.makeQualified(fs.getUri(), fs.getWorkingDirectory());
boolean isRoot = q.toUri().getPath().equals("/");
if (isRoot && !recursive) {
  // skip or list-and-delete children instead
}

Try / catch

try {
  fs.delete(f, false);
} catch (PathIOException e) {
  if ("Cannot delete root path".equals(e.getOperation())) { /* root guard hit: clean children instead */ }
  else throw e;
}

Prevention

When it happens

Trigger: fs.delete(new Path("/"), false) (recursive=false) when the bucket contains any objects; equivalently fs.delete(path-to-bucket-root, false) after qualification.

Common situations: Cleanup code that walks to the filesystem root and deletes it; tools that call delete(path, false) on directories expecting POSIX rmdir-of-empty behavior hitting a non-empty bucket; miscomputed paths that collapse to '/'.

Related errors


AI-assisted analysis of apache/hadoop@2add963021 (2026-08-22). Data as JSON: /api/errors/0ed8ff02a60b20b7. Report an issue: GitHub.