WP_Query freezing

From the discussion in comments to your question, here is what I suggest and recommend

Just to recap and to add everything in a constructive answer

  • The tag parameter in WP_Query accepts the term slug, not the name

  • Always reset your custom WP_Query, always. Make use of wp_reset_postdata() after your query is done, just before endif

  • You cannot use tag as your shortcode name, it is a reserved name in WordPress

I believe that your order of doing things is what is causing all of your headaches. Also, a shortcode should return its output, not echo it. Have a look at the Shortcode API on how to create a shortcode

Before you check anything in your code or if a post have tags, you actually have to get the current post. Without this, your code won’t work. So, the first thing to do is to call your global $post. Once you have that, you can pass the ID to get_the_tags

Your code should be working once this is done

Here is an example: (Caveat: untested)

add_shortcode('shortcodetag', 'tags');
function tags() {
    ob_start();
    global $post;

    $posttags = get_the_tags( $post->ID );
   if ( $posttags ) 
    {
        // Loop for each tag the custom post has
        foreach($posttags as $tag) 
        {
            $tag_name = ($tag->slug);

            // Search for posts with the same tag,
            // is a custom type and don't return
            // the current post
            $args = array(
                'post_type' => 'custom_post',
                'tag' => $tag_name,
                'post__not_in' => array( $post->ID )
                );

            $query = new WP_Query( $args );

            // Make sure we got results
            if( $query->have_posts() ) 
            {
                // Loop through each returned post
                while ( $query->have_posts() )
                {
                    $query->the_post(); // Returns null

                    //LOOP ELEMENTS

                }
                wp_reset_postdata();
            }
        }
    }

     $myvariable = ob_get_clean();

     return $myvariable;

}

EDIT

As @TomJNowell suggested, I don’t really see the use of a shortcode here. You’ll be doing a do_shortcode in your single template. This is exactly the same as just calling the function directly.

Here is an example to demonstrate this, and if I understand your comment correctly, an example how to display certain post elements using template tags

function tags() {
global $post;

$posttags = get_the_tags( $post->ID );
   if ( $posttags ) 
    {
        // Loop for each tag the custom post has
        foreach($posttags as $tag) 
        {
            $tag_name = $tag->slug;

            // Search for posts with the same tag,
            // is a custom type and don't return
            // the current post
            $args = array(
                'post_type' => 'custom_post',
                'tag' => $tag_name,
                'post__not_in' => array( $post->ID )
                );

            $query = new WP_Query( $args );

            // Make sure we got results
            if( $query->have_posts() ) 
            {
                // Loop through each returned post
                while ( $query->have_posts() )
                {
                    $query->the_post(); // Returns null
                        //LOOP ELEMENTS
                    the_title(); //display post title
                    if ( has_post_thumbnail() ) { // check if the post has a Post Thumbnail assigned to it.
                        the_post_thumbnail(); //display the featured image
                    } 
                    the_content(); //displays the post's content

                }
wp_reset_postdata();
            }
        }
    }
}