{"record":{"id":"87297afc2de71072","repo":"apache/superset","slug":"cycle-detected-uuid-appears-in-its-ancestry","errorCode":null,"errorMessage":"Cycle detected: {uuid} appears in its ancestry","messagePattern":"Cycle detected: (.+?) appears in its ancestry","errorType":"validation","errorClass":"ValidationError","httpStatus":422,"severity":"error","filePath":"superset/commands/dataset/update.py","lineNumber":452,"sourceCode":"    \"\"\"\n    Additional folder validation.\n\n    The marshmallow schema will validate the folder structure, but we still need to\n    check that UUIDs are valid, names are unique and not reserved, and that there are\n    no cycles.\n    \"\"\"\n    if not is_feature_enabled(\"DATASET_FOLDERS\"):\n        raise ValidationError(\"Dataset folders are not enabled\")\n\n    queue: list[tuple[FolderSchema, list[UUID]]] = [(folder, []) for folder in folders]\n    seen_uuids = set()\n    seen_fqns = set()  # fully qualified folder names\n    while queue:\n        obj, path = queue.pop(0)\n        uuid, name = obj[\"uuid\"], obj.get(\"name\")\n\n        if uuid in path:\n            raise ValidationError(f\"Cycle detected: {uuid} appears in its ancestry\")\n\n        if uuid in seen_uuids:\n            raise ValidationError(f\"Duplicate UUID in folder structure: {uuid}\")\n        seen_uuids.add(uuid)\n\n        # folders can have duplicate name as long as they're not siblings\n        if name:\n            fqn = tuple(path + [name])\n            if name and fqn in seen_fqns:\n                raise ValidationError(f\"Duplicate folder name: {name}\")\n            seen_fqns.add(fqn)\n\n            # Allow default folders (by UUID) to use reserved names\n            if (\n                name.lower() in {\"metrics\", \"columns\"}\n                and str(uuid) not in DEFAULT_FOLDER_UUIDS\n            ):\n                raise ValidationError(f\"Folder cannot have name '{name}'\")","sourceCodeStart":434,"sourceCodeEnd":470,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/commands/dataset/update.py#L434-L470","documentation":"Raised inside validate_folders()'s BFS when a folder uuid reappears in its own ancestry path — the tree built from client-supplied folders has a cycle. The path list accumulates ancestor uuids during traversal and this check fires when the current node's uuid is already in that path.","triggerScenarios":"PUT /api/v1/dataset/{id} with a folders payload where a child references an ancestor as its own descendant (e.g. folder A lists folder B under children, and B lists A under children), directly or transitively.","commonSituations":"Hand-edited folder JSON, or frontend state corruption after drag-and-drop re-parenting produces a loop. Programmatic folder generation that links the last node back to the root.","solutions":["Inspect the folders payload and break the loop: remove the back-reference that closes the cycle (the uuid in the message is the offending node).","Regenerate the folder tree from the server's current state (GET the dataset, modify only the intended node) rather than building it from scratch.","Add a pre-flight cycle check in your client (see validationCode)."],"exampleFix":"// before\nfolders = [{uuid: A, children: [{uuid: B, children: [{uuid: A}]}]}]  // cycle A->B->A\n// after\nfolders = [{uuid: A, children: [{uuid: B}]}]","handlingStrategy":"validation","validationCode":"def has_cycle(folders):\n    def visit(node, path):\n        if node[\"uuid\"] in path:\n            return True\n        return any(visit(c, path + [node[\"uuid\"]]) for c in node.get(\"children\", []))\n    return any(visit(f, []) for f in folders)\n\nassert not has_cycle(payload[\"folders\"]), \"cycle in folder tree\"","typeGuard":null,"tryCatchPattern":"try:\n    UpdateDatasetCommand(...).run()\nexcept DatasetInvalidError as ex:\n    if any(\"Cycle detected\" in str(e) for e in ex.exceptions):\n        rebuild_tree_from_server_state()","preventionTips":["Never hand-nest folder JSON; mutate the tree returned by GET.","Run a DFS cycle check in clients that support drag-and-drop re-parenting.","Log the offending uuid from the message when it fires."],"tags":["dataset","folders","validation","cycle"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}