Site’s admin created with wpmu_create_blog accessible only on 2nd attempt

TLDR

user_roles option is broken, it can be fixed by updating the db, see the solution below.

Who & Why

I was able to recreate your issue and analyze it. As you can see in the error it is clearly a matter of permissions and capabilities, and it turns out that the whole thing is because wp_xx_user_roles option.

When avoiding the regular registration process, as you are trying to do, the globals $wpdb and $wp_roles are already exist, and when you use wpmu_create_blog from within your site, the very-important option wp_xx_user_roles (the xx is for the blog id) is written with the current viewed blog id, and not the new one just created.

My guess is that when creating a new blog from the registration page, wordpress is not considering any blog to be the current blog, so when it uses a brand new wp_roles class instance, instead of using the already-set globals $wpdb and $wp_roles;

I couldn’t find any civil way to address this issue, so we’ll do it ugly.
The ugly solution is to update the blog id in user_roles option name after creating a new blog, like so:

    function replace_wrong_user_roles_option( $new_blog_id ){
        global $wpdb, $wp_roles;

        $current_blog_id = get_current_blog_id();
        if( $current_blog_id == $new_blog_id )
            return true;

        $prefix_length        = strlen( $wpdb->prefix );
        $blog_prefix_length   = strlen( $new_blog_id.'_' );
        $blog_prefix_position = strrpos ( $current_prefix, $new_blog_id.'_' );

        // only on new wpmu, when main blog prefix is without the blog_id
        if($current_blog_id == 1 && !$blog_prefix_position){
            $new_blog_prefix = $wpdb->prefix.$new_blog_id.'_';
        }else{
            $new_blog_prefix = str_replace(get_current_blog_id(), $new_blog_id, $wpdb->prefix);
        }

        return $wpdb->update(
            $new_blog_prefix.'options',
            array( 'option_name' => $new_blog_prefix.'user_roles' ),
            array( 'option_name' => $wpdb->prefix.'user_roles' )
        );
    }

And you can insert that function call right after you create your blog:

    $new_blog_id = wpmu_create_blog( $newdomain, $path, $title, $user_id, $meta, get_current_network_id() );
    replace_wrong_user_roles_option( $new_blog_id );
    add_user_to_blog($new_blog_id, get_current_user_id(), 'my_custom_role');
    $blog = get_blog_details($new_blog_id,true);
    $blog_url = $blog->siteurl;
    $redirect_url = $blog_url . '/wp-admin';

    wp_redirect( add_query_arg(array(
        'custom_notice' => 'a_custom_notice'
    ),$redirect_url));