I ran into exactly the same problem although, i didn't readily link it to the change to Wordpress 4.0.
I've got several custom post types and only a few of them were returning 404 pages. It was only after seeing your original question that i realised that the problem was with those custom post types that were set to 'hierarchical' => true. Once i set 'hierarchical' => false, I could view the "problem" custom post.
However, that also meant that I lost some of the URL/permalink structure that I had when hierarchical was set to true. So, "custom-post-type\parent-title\post-title" became "custom-post-type\post-title".
I ended up setting a "post_type_link" filter to add the parent back into the link.
I also have a rewrite rule setup for this revised link structure.
That solution was inspired by this post: https://wordpress.stackexchange.com/questions/136786/parent-cpt-child-custom-post-type-url-permalink-relationship
Although I didn't used a "%" parameter in the rewrite slug in the register_post_type.
Instead, in the post_type_link function, i search for the slug and replace it with the original slug + any required prefix. I found that way i could keep the original slug in the link, which isn't 'contaminated' with the "%" parameter which, means the link is still useful for displaying the archive page and also the "%" parameter doesn't show up in breadcrumbs.
Extract from the register_post_type:
'rewrite' => array(
'slug' => 'dresses'// 'dresses/%designer%'
),
post_type_link filter:
add_filter("post_type_link","rewrite_dress",10,2);
function rewrite_dress($link, $post) {
if(get_post_type($post) === "dress") {
$designer = get_post($post->post_parent);
$designer = $designer->post_name . "/";
if(!(check_not_empty($designer))) {
$designer = "";
}
//$link = str_replace("%designer%",$designer,$link);
$link = str_replace("dresses/","dresses/" . $designer,$link);
}
return $link;
/*
originally the slug had a reference in it e.g. %designer%
we could then do a string replace on it to insert the designer
but, if you used the breadcrumbs to view the root e.g. /dresses
then it would actually appear as /dresses/%designer% which isn't wanted.
*/
}
Rewrite rule:
function rewrite_dress_rules(){
global $wp_rewrite;
/* /dresses/designer/dress */
$q = "dresses/(.*)/(.*)";
$q = "dresses/([^/]*)/([^/]*)/?";
add_rewrite_rule($q,'index.php?post_type=dress&designer=$matches[1]&dress=$matches[2]','top');
// $wp_rewrite->flush_rules(); // !!!
}
add_action("init", 'rewrite_dress_rules' );
That all kind of works for me. At least for the time being.
I'll have to keep an eye on future Wordpress updates to see they have any further impact.