As you guessed and @Rarst suspected, there’s a pagination check in wp_unique_post_slug()
for hierarchical post types:
preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug )
which will match any numeric only slug, optionally preceded by “page”. To get around this you could use the 'wp_unique_post_slug'
filter, basically replicating the original code without the pagination check, eg
add_filter( 'wp_unique_post_slug', function ( $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug ) {
if ( $slug !== $original_slug && is_post_type_hierarchical( $post_type ) ) {
global $wpdb, $wp_rewrite;
$slug = $original_slug; // Undo any previous processing.
// The following is just a copy & paste of the WP code without the pagination check.
$feeds = $wp_rewrite->feeds;
if ( ! is_array( $feeds ) )
$feeds = array();
if ( 'nav_menu_item' == $post_type )
return $slug;
/*
* Page slugs must be unique within their own trees. Pages are in a separate
* namespace than posts so page slugs are allowed to overlap post slugs.
*/
$check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type IN ( %s, 'attachment' ) AND ID != %d AND post_parent = %d LIMIT 1";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID, $post_parent ) );
/**
* Filter whether the post slug would make a bad hierarchical post slug.
*
* @since 3.1.0
*
* @param bool $bad_slug Whether the post slug would be bad in a hierarchical post context.
* @param string $slug The post slug.
* @param string $post_type Post type.
* @param int $post_parent Post parent ID.
*/
if ( $post_name_check || in_array( $slug, $feeds ) || apply_filters( 'wp_unique_post_slug_is_bad_hierarchical_slug', false, $slug, $post_type, $post_parent ) ) {
$suffix = 2;
do {
$alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID, $post_parent ) );
$suffix++;
} while ( $post_name_check );
$slug = $alt_post_name;
}
}
return $slug;
}, 10, 6 );