The deploy trap mentioned in passing in an earlier post — that an index hint referencing an index MySQL doesn’t recognize yet is a hard error, not a slow query, so the index has to exist before the code that references it goes live — turns out to generalize well beyond that one sitemap fix. It came up again, differently, shipping a batch of five new indexes as part of an internal shared plugin update across a WordPress VIP-style multisite network. This time the near-miss wasn’t about sequencing an index against code. It was about trusting a diff.
TL;DR: A shared plugin update needed five new indexes across a multisite network. The actual near-miss wasn’t the indexes — it was applying a narrow version-bump diff to a branch that had silently drifted two minor versions further behind than assumed, landing a partial, subtly broken state. Fix: verify the base directly, replace the tree wholesale instead of trusting the diff, then apply the same existence-guard discipline to the index rollout itself.
What Was the Setup? Five Indexes, One Shared Plugin
A shared plugin used across the network needed a version bump that included, among other things, five new index-creation commands — covering things like sitemap last-modified lookups, taxonomy timestamps, and media MIME-type filtering, the same category of query-plan problem covered in the sitemap posts, just generalized into reusable commands rather than one-off fixes. Each command follows the same shape: a status check to report whether the index already exists, and an ensure action to create it, safe to run repeatedly.
$ wp custom-indexes status
sitemap-modified-gmt-index missing
sitemap-taxonomy-lastmod-index missing
focus-keyword-index missing
media-mime-index missing
sitemap-count-index missing
$ wp custom-indexes ensure --network --dry-run=false
Created: sitemap-modified-gmt-index
Created: sitemap-taxonomy-lastmod-index
Created: focus-keyword-index
Created: media-mime-index
Created: sitemap-count-index
--network runs it against every site in the multisite install in one pass; --dry-run=false is deliberately required rather than defaulted, so an accidental invocation without it reports what would happen instead of mutating the schema. status afterward re-checks all five against both a staging and a production-mirrored environment before anything goes near real traffic.
That part went cleanly. The near-miss happened one step earlier, validating the change on an intermediate branch before it ever reached the index commands.
What Was the Near-Miss? A Diff Assumed the Wrong Starting Point
The plan was ordinary: bump the shared plugin from one version to the next, validate the bump on a develop branch first, then open the real pull request against main. The version bump itself — call it 4.21.3 to 4.21.4 — was a small, contained diff. Cherry-picking just that diff onto develop seemed like the lower-risk move: touch less, verify faster.
develop‘s copy of the plugin wasn’t actually at 4.21.3. It was pinned two minor versions further back, at 4.19.0 — a fact the diff itself gave no indication of, because a diff only describes the distance between the two versions it was generated from, not the version of whatever it’s about to be applied to. Applying the narrow 4.21.3→4.21.4 diff on top of an actual 4.19.0 base landed exactly the changes that diff described and nothing else — silently skipping every change introduced in the two versions develop had missed in between, including includes and files the plugin’s newer code now assumed existed.
The plugin didn’t fail loudly. It failed the way missing includes usually do: warnings on load, behavior that’s subtly wrong rather than obviously broken, the kind of state that’s easy to wave off as noise during a validation pass if you’re not specifically watching for it.
The fix, once caught, was to stop treating this as a diff-application problem and treat it as a state problem: replace the plugin directory wholesale with the exact tree from the released 4.21.4 version, rather than trying to reconstruct the missing intermediate changes by hand. Confirmed clean load with zero warnings afterward, and only then did the five index commands get run against it.
Why Does This Matter More Than It Looks Like It Should?
The specific bug — a stale branch, an unexpectedly large version gap — is mundane. What generalizes is the assumption underneath it: that a diff between version X and version Y is safe to apply anywhere, because the diff itself looks small and contained. A diff only knows about X and Y. It says nothing about the actual state of the branch you’re about to apply it to, and if that branch silently drifted further behind than anyone tracked, a clean-looking small diff can land a broken partial state without raising any obvious alarm — no merge conflict, no failed patch, just missing files the new code quietly assumes are there.
The generally safer default, once a branch’s actual pinned version is in doubt: check what’s actually there before deciding whether a diff or a full-tree replacement is the right move. A version pin is a claim about state, and claims about state are worth verifying directly rather than inferring from how big or small the intended change looks on paper.
How Do You Sequence the Schema Change Itself?
Once the code was confirmed correct, the index rollout followed the same discipline covered in the earlier sitemap post: never assume a referenced index exists just because the deploy that’s supposed to create it has already shipped. The commands were run with an explicit status check both before and after ensure, against every site in the network individually, not just spot-checked on one — a network-wide --network flag makes it easy to assume uniform state across sites that may, in practice, have drifted from each other in exactly the way the branch above had.
That last point turned out to matter beyond this one rollout: the branch drift that caused the near-miss above was itself a symptom of develop having gone unsynced with main for longer than anyone had tracked, on a dependency pinned by another engineer’s unrelated, still-open work. Fixing the immediate version mismatch doesn’t fix that underlying drift — it’s worth flagging as its own follow-up, separate from the deploy that surfaced it, rather than assuming one clean bump means the branches are back in sync generally.
What’s the Generalizable Lesson Here?
- A diff describes the distance between two known versions. It says nothing about whether the branch you’re applying it to is actually at the version you assumed — verify the base directly before trusting a diff to be a safe, minimal change.
- Missing includes and silently-skipped changes from a version gap often fail as warnings and subtly wrong behavior, not hard errors. That makes them easy to miss during a validation pass unless you’re specifically checking for a clean load with zero warnings.
- When a diff turns out to be unsafe because the target is further behind than expected, replacing the whole tree with the known-good released version is more reliable than trying to reconstruct the missing intermediate state by hand.
- Schema changes — indexes especially — need existence checks both before and after the migration step, per environment, not assumed from a single successful run elsewhere in the network.
--networkflags make uniform state easy to assume and easy to be wrong about. - A version-skew incident caught during one deploy is often evidence of broader, ongoing drift between branches. Flag the underlying drift as its own task rather than treating the one bump you fixed as the whole problem solved.