Trying to add_action in a loop

As Milo hints at in their comment, custom post types can only be registered on the init hook. Therefore, if you are trying to get all the public but not builtin post types before init, then get_post_types() will return an empty array. (You can use get_post_types() to get the builtin post types before init.)

What you need to do is hook in after init and then get the post types. I’d use wp_loaded since at this point, WordPress is fully loaded.

add_action( 'wp_loaded', 'wpse290170_wp_loaded' );
function wpse290170_wp_loaded() {
  $types = get_post_types( [
      'public'   => true,
      '_builtin' => false
  ] );
  foreach ( $types as $type ){
    add_action( "publish_" . $type, 'duplicate_title_buwp_bc' ) ;
  }
}