Term archives inherit a page's Header/Footer/Layout setting when the term ID matches the page ID

Hi Charlie,

Claude Code found a bug in how Cornerstone picks the “active content” on archive pages. A taxonomy archive can end up using the page-level Header, Footer or Layout setting of a completely unrelated page, as long as the term ID and the page ID happen to be the same number. We hit it on Pro 6.9.6, and a small workaround is included below.

What we saw

A client site has two headers, assigned like this:

  • Header - Blue: Entire Site, excluding Front Page

  • Header White: Front Page

The “Studio” page (post ID 49) also has its own page setting, Header = Header White. That’s intended.

Every Work Category archive got the blue header except one: /work-category/visual-identity/, which rendered Header White. Its term ID is 49.

Steps to reproduce

  1. Create a page and set a Header (or Footer, or Layout) in its Cornerstone page settings, different from what the assignment rules would give.

  2. Find or create a term in any public taxonomy whose term ID equals that page’s post ID.

  3. Open the term archive. It renders with the page’s header instead of the one the assignment rules pick.

The same thing should happen on author archives when a user ID matches a page ID, though we haven’t tested that one.

Where it comes from

In Services/FrontEnd.php, init() sets the active content on template_redirect:


add_action('template_redirect', function() {

  $content = $this->resolver->getDocument( $this->get_post_id() );

  if ( $content ) {

    $this->assignments->setActiveContent( $content );

  }

});

get_post_id() falls back to get_queried_object_id() when it isn’t in the loop. On a term archive that returns the term ID, and getDocument() then treats it as a post ID and loads post 49. After that, Assignments::getFirstMatch() checks the active content’s explicit layoutHeader before evaluating any rules, so the page’s Header White wins.

The assignment rules themselves are fine. single:front-page is is_singular() && is_front_page(), which is false on that archive. It never gets evaluated, because the explicit page setting short-circuits it.

Suggested fix

Only resolve active content when the queried object really is a post. Something like:


add_action('template_redirect', function() {

  $queried = get_queried_object();

  if ( ! ( $queried instanceof WP_Post ) ) {

    return;

  }

  $content = $this->resolver->getDocument( $this->get_post_id() );

  if ( $content ) {

    $this->assignments->setActiveContent( $content );

  }

});

That keeps singular pages and a static posts page (where the queried object is the posts page) working as they do now. We haven’t gone through the other callers of get_post_id(), so some of them might rely on the same assumption.

Our workaround for now

We’re running this as a snippet until there’s a core fix. It reports post ID 0 through cs_element_post_id while Cornerstone resolves the active content on non-singular requests, and leaves everything else alone:


function wpu_cs_no_active_content_on_archives( $id ) {

    return 0;

}

add_action( 'template_redirect', function () {

    if ( is_singular() || ( is_home() && get_option( 'page_for_posts' ) ) ) {

        return;

    }

    add_filter( 'cs_element_post_id', 'wpu_cs_no_active_content_on_archives', 999 );

}, 0 );

add_action( 'template_redirect', function () {

    remove_filter( 'cs_element_post_id', 'wpu_cs_no_active_content_on_archives', 999 );

}, 99 );

We checked it before and after on eleven URLs: the home page, Studio, Connect, the Work archive and several category archives, in two languages. Only the affected archive changed (white header to blue), and every page with its own header setting kept it.

It’s easy to miss, since it only shows up when the IDs line up, and it gets more likely as a site collects pages with their own header or footer. Happy to send more detail if it helps.

Thanks,
Misho (Well, Claude actually) :slight_smile:

1 Like

Thanks for the detailed bug report. I’ll get this collision fixed in the next release. Have a great day.

1 Like