Previously on the CogitActive Saga:
And while Gemini had outperformed Copilot by a wide margin — clearer reasoning, better hooks, fewer hallucinations — even it had reached the limits of what it could do within the constraints I had set. It had to give up.
“But what about me?”
Introduction
After watching two confident AIs 1 turn a focused PHP requirement into a scenic tour of almost‑solutions and misplaced certainty, I turned to my readers. The challenge I laid out was simple to describe but stubborn in practice—display my “doorbell” widget exactly where the comment form would normally appear, and only when comments are closed. No template edits. No tinkering with single.php or template parts. Just hooks and whatever I could safely place inside my child theme’s functions.php. Within those boundaries, the task was to make the widget behave like a conditional stand‑in for the missing form, not a random button floating somewhere near the comments section.
The real issue is that on posts where comments are closed, and no one has ever commented, my theme (Twenty Seventeen) doesn’t load the comment template at all. When comments are closed and the comment count is zero, WordPress skips comments.php entirely. The culprit is the conditional in my single.php: it only loads the template if comments are open or if there’s at least one existing comment. Because neither condition is true, the comment template never gets included. When that happens, the standard comment hooks are never “fired.”
// If comments are open or we have at least one comment, load up the comment template.
if ( comments_open() || get_comments_number() ) :
comments_template();
endif;
Without any insights from my readers to guide the next step, this post picks up exactly where the AI adventure left off—except this time, the debugging, the digging, and whatever eventual solution emerges will have to come from one place only: me, rolling up my sleeves and working through the problem the slow, deliberate way I had been trying to avoid. No more almost‑helpful suggestions nudging me sideways. No more elegant but unusable snippets. Just the challenge itself, faced head‑on, with no shortcuts left to try.
The irony, of course, is that I’m not actually ready to get back to the real work. Time is still scarce, and my PHP—rudimentary at best six years ago—has long since dissolved into a vague recollection of syntax and half‑remembered function names. Whatever fluency I once had evaporated during CogitActive’s “survival mode,” leaving behind only the faint outline of concepts I used to understand. But waiting for the perfect moment, the perfect block of uninterrupted hours, the perfect return of long‑lost skills… that would mean waiting forever. So I decided to start anyway.
What I do still have is my logic—that stubborn, methodical part of me that refuses to let go of a problem once it has taken root. If my technical muscle memory is gone, then reasoning will have to carry the weight. And so, with nothing but that and a barely functional grasp of PHP, I began retracing the steps the AIs had taken, not to repeat their mistakes but to understand precisely where their confident detours had diverged from the actual path forward.
Hijacking the ‘Comments are closed.’ message
My first idea was to hijack the “Comments are closed.” message. Now, I was well aware this would not solve the main problem because it was not even displayed on posts with zero comments. But I just needed to confirm that the message itself was hookable. The single.php limitation could wait; for now. To test that, I turned to posts where comments were closed, and at least one comment existed, the only scenario in which my theme reliably displays the message. If I could swap it out there, then I would know the replacement itself was feasible before dealing with the harder part.
This approach wasn’t random. During my research, I stumbled across an old WordPress StackExchange thread titled Display custom text when comments are closed.
The question was straightforward: someone wanted to replace the default message with a personalized note. The lone answer provided a tiny snippet that supposedly did exactly that:
function comment_text ($arg) {
$arg['title_reply'] = __('Too Late - Comments are Closed!');
return $arg;
}
add_filter('comment_form_defaults','comment_text');
At first glance, the snippet looked promising. It suggested that the “Comments are closed.” message was just another piece of text passed through a filter — something I could intercept, rewrite, and eventually replace with my widget. But, as I mentioned earlier, I didn’t test it. Not even once. Instead, I went straight to the source and opened my comments.php template to inspect the code that actually prints the message:
// If comments are closed and there are comments, let's leave a little note, shall we?
if ( ! comments_open() && get_comments_number() && post_type_supports( get_post_type(), 'comments' ) ) :
?>
<p class="no-comments"><?php _e( 'Comments are closed.', 'twentyseventeen' ); ?></p>
<?php
endif;
This block was nearly identical to a snippet I had seen in a very old StackOverflow thread titled Remove WordPress ‘Comments are closed’ message from posts without comments, possible?
— a thread back from the era when themes displayed the message everywhere, even on posts that had never had comments at all. That thread explained how to remove the message from those empty posts. Now here I was, essentially needing to reverse that logic to restore the old behavior. So yes, this was the code I would eventually have to undo.
The second realization was… embarrassing. This was the moment I understood that I had been misreading my own single.php — specifically the condition comments_open() || get_comments_number(). My tired brain had blurred the difference between || and &&, and because of that, I had built a completely flawed logic (as you will see). And yet, the comments above the code spelled it out clearly (but I skipped that reading, unfortunately): “If comments are open or we have at least one comment” versus “If comments are closed and there are comments.” One uses OR. The other uses AND. And yes, || really does mean OR.
Why does this matter? For two reasons. The first is that, before realizing this, I had convinced myself that the single.php conditional was the culprit. In my foggy, late‑night state — running on fumes after several evenings of AI‑generated nonsense — I had somehow interpreted the OR as something else entirely. My “solution,” based on that misreading, was to remove the second part of the conditional so that comments.php would load unconditionally. Don’t try to make sense of this; there was none. It was just a tired brain trying to brute‑force its way through a problem it didn’t fully understand
You cannot remove or change the get_comments_number() check inside single.php without editing that file.Gemini
Following my own mistaken logic, I briefly convinced myself that the “solution” was to cheat with my own rules — to open single.php, remove part of the conditional, and call it a necessary compromise. And yes, that would have meant breaking my own rule by editing the theme template directly. Worse, it would have meant accepting a compromise, something I genuinely dislike; compromises are half‑measures, half‑correct and half‑incorrect, and they usually create more problems than they solve. But I was desperate enough to consider it, if only to salvage the bonus project. And yet, even if I had gone through with it, it wouldn’t have solved anything. It would have made things worse: removing that condition would have eliminated the “Comments are closed.” message everywhere, including on posts that actually had comments.
The second reason was the real nail in the coffin. If I wanted the message to appear on every post, I would need to remove the && get_comments_number() condition from comments.php — another template edit, another violation of my own rules. And even then, I would still be stuck with the original problem I had conveniently postponed: the comment template itself wasn’t loading on posts with zero comments. Fixing the message wouldn’t fix that.
At that point, the entire attempt collapsed under its own contradictions. It wasn’t even a failed experiment — it failed before I could try anything at all.
A hack that would not violate my own rules, and yet…
But this detour wasn’t a waste of time. While I was still inside comments.php, something else caught my eye. At the very end of the file — after all the conditionals, after the message logic, after everything — sat a single, unassuming line:
comment_form();
That puzzled me. If comment_form() was sitting there unconditionally, why wasn’t the form appearing every time? After all, single.php uses an OR condition (comments_open() || get_comments_number()) to decide whether to load comments.php. That means the template does load even when comments are closed, as long as at least one comment exists. And that, of course, is why the “Comments are closed.” message has its own extra conditional checking whether comments are closed. So if the template loads, and comment_form() is right there at the bottom, why doesn’t the form show up in those situations where the form itself is not allowed to appear — that is, when comments are closed, but some comments were published previously?
To answer that, I had to find where comment_form() actually lives. And that turned out to be harder than expected. First, the file wasn’t in my child theme, so I had to dig through WordPress core. Second, the file itself — comment-template.php — is long. Very long. It took me ages to scroll through it and locate the part that mattered, which, fittingly, was also at the very end:
function comment_form( $args = array(), $post = null ) {
$post = get_post( $post );
// Exit the function if the post is invalid or comments are closed.
if ( ! $post || ! comments_open( $post ) ) {
/**
* Fires after the comment form if comments are closed.
*
* For backward compatibility, this action also fires if comment_form()
* is called with an invalid post object or ID.
*
* @since 3.0.0
*/
do_action( 'comment_form_comments_closed' );
return;
}
And there it was — the perfect hook. Maybe. Or at least, it looked perfect: comment_form_comments_closed. It was the one hook that seemed to align naturally with my requirements, as if WordPress had anticipated this very use case.
do_action( ‘comment_form_comments_closed’ )
Fires after the comment form if comments are closed.
If the only thing preventing comments.php from loading on posts with zero comments was the get_comments_number() check in single.php, then maybe I could simply trick WordPress into believing that 0 was actually 1. In other words, cheat the conditional. If I could make the theme think there was at least one comment, it would dutifully load the template, and from there I could use the hook I had just discovered.
I didn’t even bother testing whether that hook would let me display my widget on posts that already had comments. I jumped straight to the question of how to fake the comment count. And, true to form, I took another shortcut. Instead of reasoning it out myself, I asked Copilot — the external web client this time — whether such a trick was even possible. In a fraction of a second, it produced an answer. Actually, more than the answer I was expecting. It came with caveats and warnings:
Filter get_comments_number() so the if passes
What it does: Make get_comments_number() return 1 (or any positive number) for posts that are closed and have zero comments. The theme’s if then evaluates true and comments_template() runs as if there were comments.
Code (drop into functions.php)
add_filter( 'get_comments_number', 'my_fake_comments_number', 10, 2 );
function my_fake_comments_number( $count, $post_id ) {
// Only on frontend single posts
if ( is_admin() || ! is_singular( 'post' ) ) {
return $count;
}
// Only when comments are closed and there are actually zero comments
if ( ! comments_open( $post_id ) && 0 === (int) $count ) {
// Return 1 so the theme thinks there is at least one comment
return 1;
}
return $count;
}
Important caveats:
- This changes the reported comment count across the frontend while the filter is active. That can affect:
- displayed comment counts in templates and widgets,
- plugins that rely on the real comment count,
- feeds or other logic that reads comment counts.
- You must be careful to limit the filter (e.g., only on is_singular('post') and ! is_admin()), and test thoroughly.
- Some themes/plugins may still behave unexpectedly because they expect a real comment list when the count is positive.
If you choose this route
- Consider making the filter active only during the template rendering window (add it on template_redirect and remove it on shutdown), or only for the specific template part. That reduces side effects.
Of course, I didn’t try the code. But I did take its warning seriously and went back to inspect my comments.php line by line. It didn’t take long to realize that faking get_comments_number() was brittle and fundamentally wrong. Let me give you two examples. The first one you already know — at least if you’ve been following this post with the attention of a diligent student. Spoofing the comment count would bring back the “Comments are closed.” message everywhere. That sounds good, right? In fact, it is good… or at least it would have been, if the second consequence hadn’t immediately killed the idea.
Here is the part of comments.php that stopped my enthusiasm cold:
// You can start editing here -- including this comment!
if ( have_comments() ) :
?>
<h2 class="comments-title">
<?php
$comments_number = get_comments_number();
This is where the whole hack collapses. The template needs the comment count. If I tricked WordPress into believing the count was “1,” the theme would look for a real comment loop. And since there isn’t one, the logic falls apart immediately. The template would behave as if comments existed, but the underlying data wouldn’t match. In other words, the hack wouldn’t just be fragile — it would be internally inconsistent.
Now, did I give up on the idea entirely? Of course not. I tried to salvage it. And with Copilot’s help, we ended up with a trick — an undeniably clever one, and surprisingly elegant for something born out of a hack‑on‑top‑of‑a‑hack. The idea was to create a self‑destructing filter: one that lies exactly once, just long enough to fool the theme into loading comments.php, and then removes itself before anything else can rely on the fake value. Here’s what we came up with:
add_action(
'loop_start',
function() {
add_filter( 'get_comments_number', 'twentyseventeen_gatekeeper_filter' );
}
);
function twentyseventeen_gatekeeper_filter( $count ) {
if ( is_single() && ! comments_open() && $count == 0 ) {
// Remove ourselves immediately so we never lie again for the rest of the page
remove_filter( 'get_comments_number', 'twentyseventeen_gatekeeper_filter' );
return 1;
}
return $count;
}
The concept is simple but surprisingly high‑level: intercept the comment count only once, return a fake “1” to satisfy the theme’s conditional, and then immediately remove the filter so the rest of the page sees the real value. A one‑shot deception. A controlled lie. A WordPress‑style smoke bomb.
“Does it work?”
Well… that answer belongs to the next post.
To be continued…
1 Not all AIs are created equal—believe me. That’s why it’s worth clarifying that I used Copilot in Edge and Gemini (specifically the AI mode on google.com). In other words, not exactly the most powerful tools available. They’re solid entry‑level AIs: free to use, no login required, and perfectly fine for basic tasks like answering simple questions, summarizing text, or casual brainstorming. But they don’t compare to Pro‑tier AI models that offer far more power, speed, and versatility. Their biggest limitation is that they rely on standard models with fairly basic reasoning. That’s where the cracks show. They struggle with complex logic, multi‑step problem‑solving, and advanced coding tasks—anything that requires deeper analysis or sustained reasoning. ^
2 If I had taken a moment to double‑check that snippet, I would have realized it wouldn’t work at all. The comment_form_defaults filter only affects the text elements of the active comment form—meaning the form must actually be open for those defaults to matter. And title_reply isn’t the “Comments are closed.” message; it’s simply the “Leave a Reply” heading that appears above the input fields. In other words, the snippet customizes a form that doesn’t exist in my scenario, and it targets the wrong piece of text anyway. ^

