Skip to content

fix(check): orphan-venues count_refreshed action persists the refreshed cache via direct wpdb update + cache invalidation#285

Open
chubes4 wants to merge 1 commit into
mainfrom
fix-orphan-count-cache-persistence
Open

fix(check): orphan-venues count_refreshed action persists the refreshed cache via direct wpdb update + cache invalidation#285
chubes4 wants to merge 1 commit into
mainfrom
fix-orphan-count-cache-persistence

Conversation

@chubes4
Copy link
Copy Markdown
Member

@chubes4 chubes4 commented May 19, 2026

Fixes #284

Summary

wp data-machine-events check orphan-venues --apply correctly detects venue terms whose wp_term_taxonomy.count = 0 is stale (real relationship rows exist in wp_term_relationships), reports them with action count_refreshed, and previously called wp_update_term_count_now( array($term_taxonomy_id), 'venue' ). The function call returned without error but did not actually persist the refreshed count.

Production repro (from #284)

After a full --apply run today:

SELECT term_id, tt.count AS cached,
  (SELECT COUNT(*) FROM wp_term_relationships
   WHERE term_taxonomy_id = tt.term_taxonomy_id) AS real
FROM wp_term_taxonomy tt WHERE term_id = 30968;
-- 30968   cached=0   real=2   ← stale; "refreshed" but cache didn't move

SELECT term_id, tt.count AS cached,
  (SELECT COUNT(*) FROM wp_term_relationships
   WHERE term_taxonomy_id = tt.term_taxonomy_id) AS real
FROM wp_term_taxonomy tt WHERE term_id = 40065;
-- 40065   cached=0   real=1   ← same

99 stale-cache terms were detected in today's audit; none of their cached counts actually got refreshed.

Operationally harmless (the count_refreshed action's effect is "skip deletion" — which is still correct, the term was not really an orphan), but a real correctness bug in the diagnostic and an admin-UI cosmetic mismatch.

Fix layer applied: Layer 3 (direct $wpdb->update + explicit cache invalidation)

Investigation findings:

  • CheckOrphanVenuesCommand calls wp_update_term_count_now() at the count-refresh branch (was line 232).
  • Venue_Taxonomy::register_venue_taxonomy() does not register a custom update_count_callback, so the default WP counter is in play. Layer 2 (fix a misconfigured custom callback) is not applicable.
  • Layer 1 (just add clean_term_cache() + wp_cache_delete() after the existing helper call) might have been sufficient, but the production failure mode is ambiguous — could be Redis intercepting the cached terms:N value, could be the default counter's publish/private/inherit post-status filter excluding rows we already detected, could be something else entirely. The most defensive shape — and the one explicitly recommended in the issue's "Fix direction" Calendar block expands multi-date events to all dates instead of respecting occurrence dates #3 — is to bypass wp_update_term_count_now() entirely.

The new helper persist_refreshed_count() does both halves of the fix:

  1. Writes the real count directly to wp_term_taxonomy.count via $wpdb->update(). Bypasses any custom update_count_callback indirection and any post-status filtering applied by the default counter.
  2. Invalidates the surrounding term + object cache via clean_term_cache( $term_id, 'venue', true ) and wp_cache_delete( $term_id, 'terms' ) so the next get_term() call reads fresh from the database.

The diagnostic logic (stale-cache detection via real_relationship_count()) is unchanged — only the write step is fixed. The other action branches (protected_by_flow, flagged, deleted) do not touch wp_term_taxonomy.count and do not need cache invalidation; they were intentionally left alone.

Tests

Two new regression tests in tests/Unit/CheckOrphanVenuesCommandTest.php:

  • test_count_refreshed_actually_persists_in_term_taxonomy — seeds a venue term with wp_term_taxonomy.count=0 and 2 real wp_term_relationships rows against publish-status data_machine_events posts; runs --apply; asserts wp_term_taxonomy.count is now 2 via a fresh $wpdb->get_var query (deliberately not via get_term(), which can return a cached value).
  • test_count_refresh_invalidates_object_cache — same seed; primes the term cache with the stale 0 value; runs --apply; asserts that a subsequent get_term($term_id, 'venue') returns ->count = 2. Proves both the DB write AND the cache invalidation work together.

Existing test_refreshes_stale_count_cache_before_deciding continues to pass — its assertions hold under the new persistence path.

Verification

  • php -l clean on both modified files.
  • homeboy audit returns only pre-existing drift findings unrelated to this change.

Bootstrap blocker

The plugin has no tests/bootstrap.php and no composer.json require-dev for phpunit / wp-phpunit. I could not execute the test suite locally in this worktree — the tests are syntactically valid and follow the same WP_UnitTestCase shape as the surrounding test file but were not run. Tests should run in CI if a workflow exists, or against a properly bootstrapped WP test harness.

Post-merge operator action

After this lands and deploys, re-run against the existing 99 stale-cache terms to actually persist them:

wp data-machine-events check orphan-venues --apply --url=events.extrachill.com

This will re-traverse the same candidates (their cached count is still 0), hit the count_refreshed branch, and this time the count column will move.

Alternative one-liner if the operator wants to fix every venue cache network-wide outside this command:

wp term recount venue --url=events.extrachill.com

Out of scope

  • The "no_repair_possible" residue in check missing-venue-addresses — separate concern.
  • Any other places in the codebase that call wp_update_term_count_now() — same hypothesized failure modes may apply, but each call site has its own context. This PR fixes only the count_refreshed branch in CheckOrphanVenuesCommand.

…date + cache invalidation

wp_update_term_count_now() was observed to not persist the refreshed
count on production: 99 stale-cache venue terms were correctly
identified (wp_term_taxonomy.count=0 with real wp_term_relationships
rows), the function was called against each, and zero of them had
their cached count actually move. term_id 30968 (real=2) and 40065
(real=1) both stayed cached=0 after the audit run.

Replace the call with a direct $wpdb->update against wp_term_taxonomy
plus explicit clean_term_cache() + wp_cache_delete() to invalidate the
surrounding object cache. This bypasses any custom update_count_callback
indirection and any post-status filtering applied by the default WP
counter — defensive against all three failure modes hypothesized in
issue #284 in a single change.

The diagnostic logic (stale-cache detection) is unchanged; only the
write step is fixed. The count_refreshed branch is the only one that
needed cache invalidation — protected_by_flow / flagged / deleted
already write through update_term_meta or wp_delete_term and do not
touch wp_term_taxonomy.count.

Adds two regression tests:
- test_count_refreshed_actually_persists_in_term_taxonomy asserts via
  a fresh $wpdb->get_var query (not get_term()) that the DB column
  itself is updated.
- test_count_refresh_invalidates_object_cache primes the term cache
  with the stale value and asserts get_term() returns the refreshed
  value, proving DB write + cache invalidation work together.

Fixes #284
@homeboy-ci
Copy link
Copy Markdown
Contributor

homeboy-ci Bot commented May 19, 2026

Homeboy Results — data-machine-events

Audit

audit — passed

  • intra-method-duplication — 2 finding(s)
  • duplication — 1 finding(s)
  • test_coverage — 1 finding(s)
  • Total: 4 finding(s)

Deep dive: homeboy audit data-machine-events --changed-since 1012091

Tooling versions
  • Homeboy CLI: homeboy 0.188.1+2eda4f8
  • Extension: wordpress from https://github.com/Extra-Chill/homeboy-extensions
  • Extension revision: 00d51bee
  • Action: Extra-Chill/homeboy-action@v2

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.

fix(check): orphan-venues count_refreshed action does not persist the refreshed cache

1 participant