langflow-ai/langflow · warning · HTTPException

A role cannot be its own parent

Error message

A role cannot be its own parent

What it means

Raised by PATCH /api/v1/authz/roles/{role_id} when parent_role_id in the payload equals the role's own id. Self-parenting is rejected with HTTP 400 before any cycle walk, as the trivial cycle case.

Solutions

  1. Exclude the role being edited from its own parent dropdown options
  2. In scripts, assert parentId !== roleId before PATCH
  3. If cloning a role, leave parent_role_id null or point it at the true parent, never the new/existing row id

Example fix

// before
await api.patch(`/authz/roles/${id}`, { parent_role_id: id });

// after
if (parentId === id) throw new Error('role cannot be its own parent');
await api.patch(`/authz/roles/${id}`, { parent_role_id: parentId });
Defensive patterns

Strategy: type-guard

Validate before calling

if (parentRoleId === roleId) throw new Error('role cannot be its own parent');

Type guard

const isValidParent = (parentId: string | null, selfId: string): boolean =>
  parentId === null || parentId !== selfId;

Prevention

When it happens

Trigger: PATCH {"parent_role_id": "<same-uuid-as-path>"} — typically a form bug where the parent dropdown's selected value defaults to the role being edited.

Common situations: Admin UI edit forms that pre-select the current role in the parent picker, or scripts that copy the role row and reuse its id for parent_role_id when cloning.

Related errors


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

Appendix: source

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

    if role is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Role not found")
    if role.is_system:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="System roles cannot be modified",
        )

    # Use presence checks (model_fields_set) rather than ``is not None`` so PATCH
    # can clear nullable fields. An explicit ``"description": null`` in the body
    # marks the field as set and assigns None; omitting it leaves the row alone.
    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.

View on GitHub (pinned to 976ec789d2)