You open the “Updates” page in WordPress admin and see that Elementor Pro / ACF Pro / vk-blocks-pro have updates available. Then you run wp plugin update --all from your automation, and those exact plugins don’t update.
The asymmetry — “the admin sees it; WP-CLI doesn’t” — traces back to how WordPress detects updates and how premium plugins layer proprietary update mechanisms on top of that. Here’s the mechanism and how an automation tool can adapt.
WordPress update detection is held by a transient cache
WordPress doesn’t decide “is there an update?” on every request. It caches the answer in the wp_options table as a transient, valid for up to 12 hours. The key entries:
_site_transient_update_plugins— latest plugin versions_site_transient_update_themes— themes_site_transient_update_core— core
If those transients are stale, even a version that just shipped on wordpress.org reads as “no update needed.” WP-CLI consults the same transients, so stale cache = WP-CLI misses the update too.
In one customer environment on ConoHa WING, we reproduced “version X is on wordpress.org, WP-CLI doesn’t see it” directly.
Trap 1 and fix 1 — force-refresh the transients before listing updates
The first step was simple: at the start of a maintenance run, delete the three transients before querying the update list.
def _flush_update_transients(self, conn, wp_cmd, wp_path, site_name):
"""Delete update transients between DB backup and update step"""
for t in ("update_plugins", "update_themes", "update_core"):
conn.run(
f"{wp_cmd} transient delete {t} --path={wp_path}",
warn=True, hide=True,
)
Deleting the transients triggers a rebuild on the next wp plugin list --update=available. During the rebuild, WordPress reaches out to wordpress.org, so the returned list reflects the current release state.
That handled the “we were just reading a stale cache” cases. But it didn’t help with premium plugins.
Trap 2 — with --skip-plugins, Pro updater filters never register
This is the deeper issue. Premium plugins like Elementor Pro / ACF Pro / LayerSlider ship their own license-server-driven update mechanism. They don’t go through wordpress.org.
The mechanism: during bootup, the plugin registers a pre_set_site_transient_update_plugins filter, which checks its own license server for the latest version during transient construction and injects the result into the transient.
But our tool ran all WP-CLI operations with --skip-plugins --skip-themes as a safeguard against Fatal-error plugins (the rationale is in a separate post). With plugins not loaded at boot, the Pro plugin’s filter never registers. The Pro updater can’t inject its “update available” entry, and wp plugin list --update=available keeps missing those updates.
A/B testing on real environments — Xserver, ConoHa WING, and Heteml — reproduced the symptom across all three host families, and across three Pro plugins (Elementor Pro, vk-blocks-pro, LayerSlider). Running every WP-CLI operation in safe mode was the structural cause of Pro detection being blocked.
Fix 2 — split WP-CLI operations into “needs plugins loaded” / “doesn’t”
The final resolution: divide WP-CLI operations into two groups:
- Operations that need plugins loaded (update-list query, latest-version check, transient construction) → drop safe mode
- Operations that don’t need plugins loaded (file replacement, rollback’s
wp plugin install --version=X --force) → keep safe mode
For rollbacks specifically, we switched to --skip-plugins=<name-of-broken-plugin> to skip only the offending plugin by name. That preserves “protection against Fatal-error plugins” while letting Pro updater filters fire on everything else.
# Listing updates (needs plugins loaded; Pro updaters fire)
wp plugin list --update=available --path=...
# Rollback (skip only the broken one)
wp plugin install acme-pro --version=1.2.3 --force \
--skip-plugins=broken-plugin --path=...
The targeted --skip-plugins=<name> form behaves differently from bare --skip-plugins (which skips all plugins) — easy to miss. The WP-CLI docs treat it as a side note, but the distinction is what makes “Fatal protection + proprietary updater compatibility” coexist.
Takeaway — without looking at both cache and load control, the puzzle doesn’t close
“WP-CLI misses updates” looks like a single phenomenon on the surface, but internally it was two layers stacked:
- Reading a stale transient → fixed by force-refresh before the update query
- Pro updater filters didn’t register under safe mode → fixed by splitting operations into load-required / not
The --skip-plugins --skip-themes flag pair is a very useful safe mode for WP-CLI, but leaving it on for every operation comes at the cost of detection visibility into plugins with proprietary update mechanisms. When both Fatal protection and Pro-updater compatibility are required, splitting per operation is where it lands.