WordPress 管理画面の「更新」メニューを開くと、Elementor Pro / ACF Pro / vk-blocks-pro などの 有料プラグインに更新通知が出ている。けれど自動化ツールから wp plugin update --all を叩くと、それらだけが更新されない。
「管理画面では更新できるのに、WP-CLI からは見逃される」というこの非対称、原因は WordPress の更新検知の仕組みと、有料プラグインが採用する 独自更新メカニズムの噛み合わせにあります。今回は、その仕組みと、自動化ツール側でどう対処したかを書き残しておきます。
WP の更新検知は transient cache が握っている
WordPress は更新可否を毎リクエスト判定するわけではなく、wp_options テーブルの transient(一時キャッシュ)に最大 12 時間溜め込んで使い回します。代表的なキーは:
_site_transient_update_plugins— プラグインの最新バージョン一覧_site_transient_update_themes— テーマ_site_transient_update_core— コア
これらの transient が古いまま放置されていると、wordpress.org でリリース済みの最新版でも「更新の必要なし」と判定されます。WP-CLI も同じ transient を参照しているので、キャッシュが古ければ WP-CLI も見逃す。
実際、ConoHa WING の顧客環境で「wordpress.org にはバージョン X が出ているのに WP-CLI が拾わない」現象を実機で再現していました。
罠 1 と解決 1 — 更新前に transient を強制リフレッシュ
最初の対処は、メンテナンス開始時に 3 つの transient を delete してから更新一覧取得する、というもの。
def _flush_update_transients(self, conn, wp_cmd, wp_path, site_name):
"""DB バックアップ後・更新前に transient を delete"""
for t in ("update_plugins", "update_themes", "update_core"):
conn.run(
f"{wp_cmd} transient delete {t} --path={wp_path}",
warn=True, hide=True,
)
delete された transient は、次回 wp plugin list --update=available を叩いたタイミングで再構築されます。再構築時には wordpress.org への問い合わせが入るので、最新のリリース状況を反映した結果が返ってくる。
これで「単に古いキャッシュを引いていただけ」のケースは解消しました。ただ、これだけでは Pro プラグインは拾えていませんでした。
罠 2 — --skip-plugins で Pro updater のフィルターが登録できない
ここからが本題です。Elementor Pro / ACF Pro / LayerSlider などの 有料プラグインは独自のライセンスサーバー経由更新を実装していて、wordpress.org とは別の更新フローを持っています。
仕組みとしては、プラグインが起動時に pre_set_site_transient_update_plugins フィルターを登録し、transient 構築時に自分のライセンスサーバーへ「最新版はあるか?」を問い合わせ、結果を transient に注入する設計です。
ところが当時の私たちは、Fatal プラグインがあっても WP-CLI 自身が落ちないよう、全 WP-CLI 操作を --skip-plugins --skip-themes 付きで実行していました(この経緯は別記事)。これでプラグイン本体がブート時にロードされないため、Pro プラグインのフィルター登録も発火しない。結果として、Pro updater が「最新版あります」と transient に書き込めず、wp plugin list --update=available は Pro 更新を見逃し続けていました。
実機 A/B 検証では Xserver / ConoHa WING / Heteml の 3 系統で再現し、Elementor Pro / vk-blocks-pro / LayerSlider の 3 系統で同じ症状を確認できました。全 WP-CLI 操作を一律で safe-mode にしていたことが、Pro 検出ロックを生んでいた構造です。
解決 2 — プラグインロード「必要 / 不要」の切り分け
最終的に、WP-CLI 操作を 2 群に分けることで対処しました:
- プラグインロードが必要な操作(更新一覧取得・最新版チェック・transient 構築)→ safe-mode を外す
- プラグインロードが不要な操作(ファイル置換・rollback の
wp plugin install --version=X --force)→ safe-mode を保持
特にロールバック実行時は、--skip-plugins=<壊れたプラグイン名> で 問題を起こした 1 件だけを指定的にスキップする形に変更。これで「Fatal プラグインからの保護」と「Pro updater の発火」を両立できます。
# 更新一覧取得(プラグインロード必要・Pro updater 発火)
wp plugin list --update=available --path=...
# ロールバック実行(壊れた 1 件だけ skip)
wp plugin install acme-pro --version=1.2.3 --force \
--skip-plugins=broken-plugin --path=...
ロールバック時の --skip-plugins=<name> という指定的スキップは、--skip-plugins(引数なし=全プラグインスキップ)と挙動が違うのが分かりにくいポイントです。WP-CLI のドキュメントでもさらっと触れられている程度の挙動ですが、Fatal プラグイン対策と独自 updater 互換の両立にはこの細かい差が効いてきます。
まとめ — キャッシュとロード制御の両方を見ないと完結しない
「WP-CLI が更新を見逃す」現象は、表面的には 1 つに見えても、内部では 2 つのレイヤーの噛み合わせ問題でした:
- 古い transient を引き続けていた → 強制リフレッシュで解消
- safe-mode で Pro updater のフィルター登録が発火しなかった → ロード必要 / 不要の切り分けで解消
--skip-plugins --skip-themes は WP-CLI の安全モードとして非常に有用なフラグですが、「常時 on」で運用すると独自更新メカニズムを持つプラグインの検出を犠牲にする、というトレードオフが存在します。Fatal プラグイン対策と Pro updater 互換、両方が必要な現場では、操作ごとに切り分けるのが落としどころでした。