Skip to content

Fix: register cache-invalidation hooks under WP-CLI when there is no request URI - #1068

Open
thisismyurl wants to merge 1 commit into
Automattic:trunkfrom
thisismyurl:fix/register-invalidation-hooks-under-cli
Open

Fix: register cache-invalidation hooks under WP-CLI when there is no request URI#1068
thisismyurl wants to merge 1 commit into
Automattic:trunkfrom
thisismyurl:fix/register-invalidation-hooks-under-cli

Conversation

@thisismyurl

Copy link
Copy Markdown
Contributor

Hey Donncha,

#1007 — the report is that scheduled posts published through WP-CLI don't clear the cache, and that's exactly what's happening.

wp_cache_postload() bails as soon as $wp_cache_request_uri is empty, which is the normal state under WP-CLI (there's no request). The early return is right for the serving side — there's no page to serve on the CLI — but it also skips wp_cache_phase2(), and that's where wpsc_register_post_hooks() wires up publish_post, transition_post_status, and the rest. So when wp-cron fires under the CLI and a scheduled post goes live, nothing is listening, and the stale page survives.

The fix registers just the invalidation hooks on that empty-URI path and leaves the serving path skipped as it was. It leans on the existing wpsc_register_post_hooks() — that only wires the content-change hooks, not the serving ones, and it's guarded by a static $done, so calling it here can't double-register on a normal request. Four lines, and they only run when there's no request URI.

For the test I added a small smoke case (WpCachePostloadTest) that sets an empty request URI, runs postload, and asserts the invalidation hooks registered. It needed add_action/has_action doubles, which the smoke bootstrap didn't have yet, so I added them next to the existing add_filter one. I checked it's a real guard — with the fix the hooks register, and reverting the four lines makes the test fail.

One thing I'm not sure about: whether you'd rather scope the registration more tightly — only under WP_CLI — instead of "whenever there's no request URI." I went with the no-URI condition because it's the actual precondition for the bug and it's what the function already branches on, but I'm happy to switch to a CLI-specific guard if you'd prefer.

Fixes #1007

(full disclosure: AI helped me identify the issue and verify my work)

…request URI

When $wp_cache_request_uri is empty (WP-CLI, and some cron runners),
wp_cache_postload() returned before wp_cache_phase2() could run, so none of
the cache-invalidation hooks registered. Publishing a scheduled post from the
CLI therefore left the stale page in the cache.

Register the invalidation hooks (via the existing idempotent
wpsc_register_post_hooks()) on the empty-request-URI path too. The serving
path stays skipped, since a request with no URI never serves a cached page.

Adds a WP-runtime-free smoke test with add_action()/has_action() doubles.

Fixes Automattic#1007
Copilot AI review requested due to automatic review settings July 1, 2026 16:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes cache invalidation when WordPress runs without a request URI (notably WP-CLI / some cron runners) by ensuring the post-change invalidation hooks are still registered even though the cache-serving path is skipped.

Changes:

  • Register wpsc_register_post_hooks() from wp_cache_postload() when $wp_cache_request_uri is empty (and caching is enabled), restoring invalidation behavior for CLI/cron contexts.
  • Add a smoke test to assert invalidation hooks are registered in the empty-URI path, plus minimal add_action() / has_action() hook doubles for the smoke tier.
  • Add a changelog entry documenting the fix.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
wp-cache-phase2.php Registers invalidation hooks even when request URI is empty, while still skipping cache serving.
tests/php/smoke/WpCachePostloadTest.php Adds regression smoke coverage for hook registration under empty request URI.
tests/php/bootstrap-smoke.php Extends smoke bootstrap with add_action() / has_action() doubles backed by the existing filter registry.
changelog/fix-cli-cache-invalidation Notes the WP-CLI scheduled-post cache invalidation fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread wp-cache-phase2.php
Comment on lines +425 to +427
// them here so content changes still clear the cache. wpsc_register_post_hooks()
// wires only the content-change hooks (not the serving ones) and is
// idempotent, so this is safe alongside the normal request path.
@donnchawp

Copy link
Copy Markdown
Contributor

This review was generated by AI.

Thanks @thisismyurl, this is careful work and the reasoning in the description is good. I went to merge it and then couldn't reproduce the bug it fixes, so I dug in. I think the hooks are already registered on the CLI and the change is a no-op in practice.

wp-cache.php:175 has had this since the first commit in 2022:

function wp_super_cache_init_action() {
	load_plugin_textdomain( 'wp-super-cache', false, basename( __DIR__ ) . '/languages' );
	wpsc_register_post_hooks();
}
add_action( 'init', 'wp_super_cache_init_action' );

init fires on every WordPress load, WP-CLI included, and wp_cache_postload() runs before it. So by the time the empty-URI branch returns, the invalidation hooks are already queued up for init.

I checked that directly on trunk, before applying your branch:

transition_post_status wpsc_post_transition: 10
publish_post wp_cache_post_edit: 0

That's from wp eval-file, so no request URI.

Then I ran the actual scenario from #1007. Two false starts worth mentioning, because they nearly convinced me the bug was real: my first attempts published the post through a loopback request to wp-cron.php, which has a request URI and never touches the branch you changed. You can see it in the debug log as /wp-cron.php?doing_wp_cron=.... I set DISABLE_WP_CRON to force the CLI process to do the work itself.

With that set, a scheduled post backdated so the event is due, and the home page cached:

wp cron event run publish_future_post
post: future -> publish
supercache/localhost/index.html -> index.html.needs-rebuild

The debug log, same PID, no request URI:

15:58:57 292  wp_cache_postload: no request uri configured. Not running.
15:58:58 292  wp_cache_post_edit: Clearing cache for post 220 on clean_post_cache
15:58:58 292  Post change: supercache enabled: deleting cache files in .../supercache/localhost/
15:58:58 292  rebuild_or_gc: rename file to .../index.html.needs-rebuild

The early return you're targeting does happen, and the cache is cleared a second later in the same process anyway.

So I don't want to merge this as a fix for #1007, and I'd rather not close that issue with it, because the reporter's site would behave exactly as before.

A few things you got right that I don't want to lose:

  • wpsc_register_post_hooks() really is idempotent and it really does wire only content-change hooks. I read it again to be sure.
  • The if ( $cache_enabled ) guard is doing more than it looks. The callee sets static $done = true on its disabled early return, so calling it unguarded while caching is off would latch that flag and suppress the later init registration for the rest of the request. Good catch, and I'd have missed it.
  • The first smoke test is a real guard. I reverted the four lines and watched it fail.

Two small things if you do carry on with the branch:

  • test_registers_no_hooks_when_cache_disabled passes either way, because wpsc_register_post_hooks() has its own cache_enabled check. I replaced the guarded call with a bare one and the suite stayed green.
  • Drop changelog/fix-cli-cache-invalidation. That directory is dead, left over from the Jetpack monorepo, and nothing reads it. Release notes come from a ### Release Notes section in the PR body. That's on us, not you, and I've opened Delete the dead changelog/ directory and document the real release-notes mechanism #1100 to delete the directory so nobody else walks into it.

The original report might still be real, I just don't think registration is the cause. Two things I'd look at next:

  • wp-cache-phase2.php:3686 dereferences $_SERVER['REQUEST_URI'] with no guard, and it's reachable under CLI when $wp_cache_refresh_single_only is set and there's no referrer.
  • wp-cache-base.php:6 falls back to get_option('home') for the host when there's no HTTP_HOST, and that also seeds $blogcacheid. On multisite the CLI run and the web request that wrote the cache can end up with different $blog_cache_dir values, so the deletes would be aimed at the wrong directory.

Was the original reporter on multisite? That's the first thing I'd ask them.

Happy to be wrong about any of this, my repro was on wp-env with PHP 8.3 and WordPress 7.1, so shout if it behaves differently on your setup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WP Super Cache: Fix $wp_cache_request_uri checks so WP CLI works properly

3 participants