apache/superset · error · ValidationError

Cycle detected: {uuid} appears in its ancestry

Error message

Cycle detected: {uuid} appears in its ancestry

What it means

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.

Source

Thrown at superset/commands/dataset/update.py:452

    """
    Additional folder validation.

    The marshmallow schema will validate the folder structure, but we still need to
    check that UUIDs are valid, names are unique and not reserved, and that there are
    no cycles.
    """
    if not is_feature_enabled("DATASET_FOLDERS"):
        raise ValidationError("Dataset folders are not enabled")

    queue: list[tuple[FolderSchema, list[UUID]]] = [(folder, []) for folder in folders]
    seen_uuids = set()
    seen_fqns = set()  # fully qualified folder names
    while queue:
        obj, path = queue.pop(0)
        uuid, name = obj["uuid"], obj.get("name")

        if uuid in path:
            raise ValidationError(f"Cycle detected: {uuid} appears in its ancestry")

        if uuid in seen_uuids:
            raise ValidationError(f"Duplicate UUID in folder structure: {uuid}")
        seen_uuids.add(uuid)

        # folders can have duplicate name as long as they're not siblings
        if name:
            fqn = tuple(path + [name])
            if name and fqn in seen_fqns:
                raise ValidationError(f"Duplicate folder name: {name}")
            seen_fqns.add(fqn)

            # Allow default folders (by UUID) to use reserved names
            if (
                name.lower() in {"metrics", "columns"}
                and str(uuid) not in DEFAULT_FOLDER_UUIDS
            ):
                raise ValidationError(f"Folder cannot have name '{name}'")

View on GitHub (pinned to f4587218dd)

Solutions

  1. 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).
  2. Regenerate the folder tree from the server's current state (GET the dataset, modify only the intended node) rather than building it from scratch.
  3. Add a pre-flight cycle check in your client (see validationCode).

Example fix

// before
folders = [{uuid: A, children: [{uuid: B, children: [{uuid: A}]}]}]  // cycle A->B->A
// after
folders = [{uuid: A, children: [{uuid: B}]}]
Defensive patterns

Strategy: validation

Validate before calling

def has_cycle(folders):
    def visit(node, path):
        if node["uuid"] in path:
            return True
        return any(visit(c, path + [node["uuid"]]) for c in node.get("children", []))
    return any(visit(f, []) for f in folders)

assert not has_cycle(payload["folders"]), "cycle in folder tree"

Try / catch

try:
    UpdateDatasetCommand(...).run()
except DatasetInvalidError as ex:
    if any("Cycle detected" in str(e) for e in ex.exceptions):
        rebuild_tree_from_server_state()

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of apache/superset@f4587218dd (2026-08-14). Data as JSON: /api/errors/87297afc2de71072. Report an issue: GitHub.