langflow-ai/langflow · warning · HTTPException

Setting this parent would create a role hierarchy cycle

Error message

Setting this parent would create a role hierarchy cycle

What it means

Raised by PATCH /api/v1/authz/roles/{role_id} after _detect_parent_cycle walks the parent chain from the proposed parent and finds the role being edited already in it. Setting that parent would create a cycle in the role hierarchy, so the request is rejected with HTTP 400 before assignment.

Source

Thrown at src/backend/base/langflow/api/v1/authz_roles.py:197

    fields_set = payload.model_fields_set

    if "parent_role_id" in fields_set:
        if payload.parent_role_id is None:
            role.parent_role_id = None
        else:
            if payload.parent_role_id == role.id:
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail="A role cannot be its own parent",
                )
            parent = await session.get(AuthzRole, payload.parent_role_id)
            if parent is None:
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail="parent_role_id does not reference an existing role",
                )
            if await _detect_parent_cycle(session, role_id=role.id, proposed_parent_id=payload.parent_role_id):
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail="Setting this parent would create a role hierarchy cycle",
                )
            role.parent_role_id = payload.parent_role_id

    if "description" in fields_set:
        # description is nullable on the DB side — None is a legitimate clear.
        role.description = payload.description

    if "name" in fields_set:
        # name is NOT NULL + unique on the DB side; reject an explicit null at
        # the boundary so the caller gets a clear 400 instead of an opaque
        # IntegrityError that the catch block below mislabels as "Name conflict".
        if payload.name is None:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="name cannot be null",
            )

View on GitHub (pinned to 976ec789d2)

Solutions

  1. Restructure the change: first set the intermediate role's parent to null (PATCH parent_role_id: null) to break the chain, then apply the intended parentage in topological order
  2. Verify with GET /authz/roles and walk parent ids client-side before submitting the move
  3. Apply hierarchy edits one hop at a time so any cycle is easy to localize

Example fix

// before
// A is ancestor of B; trying to make A's parent = B
await api.patch(`/authz/roles/${A}`, { parent_role_id: B }); // 400 cycle

// after
await api.patch(`/authz/roles/${A}`, { parent_role_id: null }); // detach
await api.patch(`/authz/roles/${B}`, { parent_role_id: A });  // desired order
Defensive patterns

Strategy: validation

Validate before calling

// walk parent chain client-side; roles fetched from GET /authz/roles
function createsCycle(roles: Role[], roleId: string, proposedParent: string): boolean {
  const byId = new Map(roles.map(r => [r.id, r]));
  let cur = byId.get(proposedParent);
  while (cur) {
    if (cur.id === roleId) return true;
    cur = cur.parent_role_id ? byId.get(cur.parent_role_id) : undefined;
  }
  return false;
}

Type guard

const isAcyclicMove = (roles: Role[], roleId: string, parentId: string): boolean =>
  !createsCycle(roles, roleId, parentId);

Prevention

When it happens

Trigger: PATCH where role A's parent is set to B while B (or any of B's ancestors) already has A as an ancestor — e.g. A→B exists and you PATCH B with parent_role_id=A. Deep chains trigger it transitively, not just two-node loops.

Common situations: Reorganizing role hierarchies where admins move a parent beneath one of its descendants, or bulk import scripts that reorder hierarchy without topological ordering.

Related errors


AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14). Data as JSON: /api/errors/0fe7abefb06463e0. Report an issue: GitHub.