{"record":{"id":"f5f1b9ace8dfe1b4","repo":"apache/hadoop","slug":"bucket-s-cannot-be-deleted","errorCode":null,"errorMessage":"Bucket %s cannot be deleted","messagePattern":"Bucket (.+?) cannot be deleted","errorType":"exception","errorClass":"InvalidRequestException","httpStatus":null,"severity":"error","filePath":"hadoop-cloud-storage-project/hadoop-huaweicloud/src/main/java/org/apache/hadoop/fs/obs/OBSCommonUtils.java","lineNumber":345,"sourceCode":"      ioe = new OBSIOException(message, exception);\n      break;\n    }\n    return ioe;\n  }\n\n  /**\n   * Reject any request to delete an object where the key is root.\n   *\n   * @param bucket bucket name\n   * @param key    key to validate\n   * @throws InvalidRequestException if the request was rejected due to a\n   *                                 mistaken attempt to delete the root\n   *                                 directory.\n   */\n  static void blockRootDelete(final String bucket, final String key)\n      throws InvalidRequestException {\n    if (key.isEmpty() || \"/\".equals(key)) {\n      throw new InvalidRequestException(\n          \"Bucket \" + bucket + \" cannot be deleted\");\n    }\n  }\n\n  /**\n   * Delete an object. Increments the {@code OBJECT_DELETE_REQUESTS} and write\n   * operation statistics.\n   *\n   * @param owner the owner OBSFileSystem instance\n   * @param key   key to blob to delete.\n   * @throws IOException on any failure to delete object\n   */\n  static void deleteObject(final OBSFileSystem owner, final String key)\n      throws IOException {\n    blockRootDelete(owner.getBucket(), key);\n    ObsException lastException = null;\n    for (int retryTime = 1; retryTime <= MAX_RETRY_TIME; retryTime++) {\n      try {","sourceCodeStart":327,"sourceCodeEnd":363,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-cloud-storage-project/hadoop-huaweicloud/src/main/java/org/apache/hadoop/fs/obs/OBSCommonUtils.java#L327-L363","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Guard your loop: stop deleting parent dirs when path depth == bucket root (path.isRoot() or path.getParent() == null).","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.","Fix key arithmetic: never produce empty keys; assert !key.isEmpty() before calling delete APIs.","Review distcp -p and 'delete empty dirs' style options when the working root is the bucket itself."],"exampleFix":"// before\nPath p = fileToDelete.getParent();\nwhile (p != null) { fs.delete(p, false); p = p.getParent(); }\n// last iteration p == obs://bucket/ -> 'Bucket ... cannot be deleted'\n\n// after\nPath p = fileToDelete.getParent();\nwhile (p != null && !p.isRoot()) { fs.delete(p, false); p = p.getParent(); }\n// stop above root; or explicitly: if (!p.isRoot()) fs.delete(p, false);","handlingStrategy":"validation","validationCode":"static boolean isBucketRoot(Path p) {\n  String path = p.toUri().getPath();\n  return path == null || path.isEmpty() || \"/\".equals(path);\n}\n// before delete:\nif (isBucketRoot(target)) throw new IllegalArgumentException(\"refusing root delete: \" + target);","typeGuard":"static Path safeObjectPath(Path p) {\n  String k = p.toUri().getPath();\n  if (k == null || k.isEmpty() || \"/\".equals(k))\n    throw new IllegalArgumentException(\"bucket-root paths are not deletable objects: \" + p);\n  return p;\n}","tryCatchPattern":"try {\n  fs.delete(objPath, false);\n} catch (InvalidRequestException e) {\n  if (String.valueOf(e.getMessage()).contains(\"cannot be deleted\")) {\n    LOG.error(\"bug: attempted root delete for {}\", objPath);\n  }\n  throw e;\n}","preventionTips":["Stop parent-cleanup loops at path.isRoot().","Compute keys with library helpers; assert non-empty before delete.","Config-lint base-dir settings so the 'root' of cleanup is never the bucket itself."],"tags":["obs","huaweicloud","delete","root-guard","path-validation"],"backgroundTag":"delete-root-of-bucket","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}