apache/hadoop · error · InvalidRequestException

Bucket %s cannot be deleted

Error message

Bucket %s cannot be deleted

What it means

OBSCommonUtils.blockRootDelete(bucket, key) is a guard invoked before object deletes: if the decoded key is empty or exactly '/' — i.e. the caller asked to delete the bucket ROOT object 'obs://bucket/' — it throws InvalidRequestException('Bucket <bucket> cannot be deleted'). The connector refuses to issue a delete that would target the root of the bucket namespace, because that maps to deleting every object or the bucket itself depending on SDK semantics.

Source

Thrown at hadoop-cloud-storage-project/hadoop-huaweicloud/src/main/java/org/apache/hadoop/fs/obs/OBSCommonUtils.java:345

      ioe = new OBSIOException(message, exception);
      break;
    }
    return ioe;
  }

  /**
   * Reject any request to delete an object where the key is root.
   *
   * @param bucket bucket name
   * @param key    key to validate
   * @throws InvalidRequestException if the request was rejected due to a
   *                                 mistaken attempt to delete the root
   *                                 directory.
   */
  static void blockRootDelete(final String bucket, final String key)
      throws InvalidRequestException {
    if (key.isEmpty() || "/".equals(key)) {
      throw new InvalidRequestException(
          "Bucket " + bucket + " cannot be deleted");
    }
  }

  /**
   * Delete an object. Increments the {@code OBJECT_DELETE_REQUESTS} and write
   * operation statistics.
   *
   * @param owner the owner OBSFileSystem instance
   * @param key   key to blob to delete.
   * @throws IOException on any failure to delete object
   */
  static void deleteObject(final OBSFileSystem owner, final String key)
      throws IOException {
    blockRootDelete(owner.getBucket(), key);
    ObsException lastException = null;
    for (int retryTime = 1; retryTime <= MAX_RETRY_TIME; retryTime++) {
      try {

View on GitHub (pinned to 2add963021)

Solutions

  1. Guard your loop: stop deleting parent dirs when path depth == bucket root (path.isRoot() or path.getParent() == null).
  2. If you truly want to empty a bucket, use delete(new Path("obs://bucket/"), true) with recursive=true — that path is governed by rejectRootDirectoryDelete, not this object-level guard — or list-and-delete keys explicitly.
  3. Fix key arithmetic: never produce empty keys; assert !key.isEmpty() before calling delete APIs.
  4. Review distcp -p and 'delete empty dirs' style options when the working root is the bucket itself.

Example fix

// before
Path p = fileToDelete.getParent();
while (p != null) { fs.delete(p, false); p = p.getParent(); }
// last iteration p == obs://bucket/ -> 'Bucket ... cannot be deleted'

// after
Path p = fileToDelete.getParent();
while (p != null && !p.isRoot()) { fs.delete(p, false); p = p.getParent(); }
// stop above root; or explicitly: if (!p.isRoot()) fs.delete(p, false);
Defensive patterns

Strategy: validation

Validate before calling

static boolean isBucketRoot(Path p) {
  String path = p.toUri().getPath();
  return path == null || path.isEmpty() || "/".equals(path);
}
// before delete:
if (isBucketRoot(target)) throw new IllegalArgumentException("refusing root delete: " + target);

Type guard

static Path safeObjectPath(Path p) {
  String k = p.toUri().getPath();
  if (k == null || k.isEmpty() || "/".equals(k))
    throw new IllegalArgumentException("bucket-root paths are not deletable objects: " + p);
  return p;
}

Try / catch

try {
  fs.delete(objPath, false);
} catch (InvalidRequestException e) {
  if (String.valueOf(e.getMessage()).contains("cannot be deleted")) {
    LOG.error("bug: attempted root delete for {}", objPath);
  }
  throw e;
}

Prevention

When it happens

Trigger: delete(path) where path.toUri().getPath() decodes to '/' or '' — e.g. new Path("obs://mybucket/") or new Path("obs://mybucket"); code that computes key = parent - child and ends with the empty string; rename/cleanup logic that deletes the 'parent directory' after emptying it and walks all the way to root.

Common situations: Recursive cleanup scripts that delete parents after removing children and forget to stop at the bucket root; temp-dir cleanup where the temp root was configured as the bucket itself; renames computing destination keys with string arithmetic that yields empty; distcp jobs asked to 'remove empty source dirs' where source is the root.

Related errors


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