2026年8月10日。Xserver に SSH で入って、サイトの棚卸し用に書いたスクリプトを wp eval-file で回そうとしたら、結果ではなく Fatal error が返ってきました。別のコマンドを試しても同じ。wp core version でさえ落ちる。WP-CLI が丸ごと死んでいます。
サイトのほうは何事もなく表示されていました。管理画面も普通に開く。壊れているのはコマンドラインだけです。しばらく WP-CLI 側を疑って、phar を入れ直そうとしたり、パスを確認したりしていました。原因が自分の書いたプラグインだと分かるまでに、しばらくかかっています。
検証したのは Xserver スタンダードプラン、SSH 接続、WordPress 7.0 系のサイトです。Web 側の PHP は 8.2 以上に設定してあり、SSH の php -v が返したのは 8.0.30 でした。


上の2枚が、この記事で起きたことの全体です。1枚目が、同じサーバーの同じプラグインなのに Web と CLI で結果が分かれた構図。2枚目が、直したあとにどこで止まるようになったか。以下は、この2枚にたどり着くまでの順番です。
WP-CLI が落ちていたのは、自分のプラグインが原因でした
エラーの本文を読み直したら、そこに自分のプラグインのパスが入っていました。サーバー名とユーザー名だけ伏せて、そのまま貼ります。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $ wp eval-file export-seo.php PHP Fatal error: Uncaught RuntimeException: Composer detected issues in your platform: Your Composer dependencies require a PHP version ">= 8.2.0". You are running 8.0.30. in /home/****/raplsworks.com/public_html/wp-content/plugins/rapls-passkey-pro/vendor/composer/platform_check.php:21 Stack trace: #0 .../vendor/composer/autoload_real.php(23): require() #1 .../vendor/autoload.php(21): RaplsPasskeyPro\Vendor\ComposerAutoloaderInit29872...::getLoader() #2 .../vendor/scoper-autoload.php(9): require_once('/home/****/...') #3 .../vendor/scoper-autoload.php(21): WP_CLI\Runner::{closure}() #4 .../plugins/rapls-passkey-pro/rapls-passkey-pro.php(61): require('/home/****/...') #5 .../public_html/wp-settings.php(589): include_once('/home/****/...') #6 phar:///usr/bin/wp/vendor/wp-cli/wp-cli/php/WP_CLI/Runner.php(1349): require('/home/****/...') (以下、WP-CLI 内部のフレームが続く) thrown in .../vendor/composer/platform_check.php on line 21 Error: このサイトで重大なエラーが発生しました。 |
スタックトレースは長いので途中を省いています。読みたいのは #4 と #0 の2行です。#4 が rapls-passkey-pro.php の61行目、つまり自分が書いた require。そこから2つ下って、#0 で Composer の platform_check.php が例外を投げています。最後の Error: このサイトで重大なエラーが発生しました。 は WordPress が出したもので、致命的エラーとして扱われたことを示しています。
途中に scoper-autoload.php が挟まっているのは、依存ライブラリの名前空間を php-scoper で書き換えているからです。自分のプラグインが読んでいるのは直接 vendor/autoload.php ではなく、その手前のファイル。ただ、どちらを読んでも最終的に platform_check.php に到達するので、今回の話には影響しません。
Composer で作った vendor/ の中には composer/platform_check.php というファイルが入っていて、vendor/autoload.php を読み込んだ瞬間に走ります。composer.json が要求している PHP のバージョンを、実行中の PHP が満たしているかを確認するだけの短いファイルです。満たしていなければ、そこで例外を投げて止まる。
止まるのは正しい動きです。依存ライブラリが 8.2 の書き方を使っているのに 8.0 で走らせれば、どのみちどこかで壊れる。手前で止めるほうが親切だという設計思想は分かります。問題は、止まり方のほうでした。
Xserver は Web と SSH で PHP のバージョンが違います
ここで効いていたのが、Xserver の仕様です。サーバーパネルで設定する PHP のバージョンは、Web からアクセスされたときに使われるものです。SSH でログインして php と打ったときに動くのは、それとは別の、サーバー全体のデフォルトになります。
知識としては知っていました。過去に .bashrc でパスを通した記憶もあります。ただ、この件で長く迷ったのは、CLI の PHP が 8.0.30 という中途半端に新しいバージョンだったからです。よく見かける事例は 5.4.16 が返ってくるパターンで、そちらなら見た瞬間に古いと分かる。8.0 は、言われなければ気づきません。動くコマンドも多い。
結果として、Web では動いてサイトは無事なのに、CLI だけが落ちるという状態になっていました。同じサーバーの、同じプラグインです。読み込む PHP が違うだけで、片方は無事、片方は全滅する。
自分のコードは、バージョンを確認せずに require していました
スタックトレースの #4 が指していた、プラグイン本体の 61 行目を見に行きました。vendor/autoload.php を、何の確認もせずに require しています。書いた当時の私は、Composer の依存を読み込むのだから当然そう書くものだと思っていたはずです。
ここに問題があると分かってから、しばらく考え込みました。プラグインが動かないこと自体は、私の想定内です。PHP 8.2 を要求すると決めたのは自分だし、満たさない環境で機能しないのは仕方がない。困るのは、動かないことではなく、動かないときにサイトごと道連れにすることのほうでした。
WordPress.org のサポートフォーラムを検索すると、同じ形の報告がいくつも出てきます。プラグインを更新した瞬間に管理画面ごと真っ白になり、リカバリーモードにも入れず、FTP でフォルダをリネームするしかなくなる。開発者が「これはこちらのミスでした。バージョンを確認して警告を出す形に直します」と返している例もありました。私が今いるのは、その返信を書く側の位置です。
直したのは、読み込む前に確認するという一点だけです
やったことは単純で、vendor/autoload.php を require する手前に、PHP のバージョンを見る分岐を置きました。足りなければ、そこで return して以降を一切実行しません。
PHP_VERSION_ID で早期に return する
PHP_VERSION_ID は 8.0.30 なら 80030 という整数を返す定数です。文字列比較より速く、書き間違いも起きにくい。8.2 以上かどうかを見るなら 80200 と比べます。この判定は Composer の autoload より前に走らせる必要があるので、プラグインファイルのほぼ先頭に置きました。
管理画面には admin_notices で伝える
黙って止まると、利用者は「入れたのに何も起きない」という状態に置かれます。それは白画面とは別の意味で不親切なので、管理画面に警告を出すようにしました。動いていないこと、必要な PHP のバージョン、いま動いている PHP のバージョン。この3つが並んでいれば、次に何をすればいいかは自分で分かります。
WP-CLI では warning を返す
コマンドラインには admin_notices が届きません。WP_CLI クラスが定義されているかを見て、あれば WP_CLI::warning() を呼ぶようにしました。修正後に Xserver で叩き直したら、Fatal error ではなくこう返ってきます。
Warning: Rapls Passkey Pro is not running: PHP 8.2 or later is required, and this is PHP 8.0.30.
そして、その後ろに wp plugin list の結果がちゃんと出る。他のプラグインの作業が止まらなくなりました。
プラグインヘッダに Requires PHP を書く
ヘッダに Requires PHP: 8.2 を書いておくと、WordPress 本体が有効化の時点で止めてくれます。管理画面から入れる限り、そもそも 8.2 未満の環境では有効化できなくなる。コードのガードとは別の層で効くので、両方書いておくのが正しい形でした。
ただ、これだけでは足りません。ヘッダが効くのは有効化のときだけです。有効化したあとにサーバーの PHP を下げた場合や、今回のように Web と CLI でバージョンが違う場合は、素通りします。実際、今回それで通り抜けられました。
そもそも 8.2 を要求していたのは誰なのか
直したあとで、要求バージョンの出所を追いました。composer.json に自分で 8.2 と書いた覚えがなかったからです。
たどっていくと、パスキーの実装に使っている web-auth/webauthn-lib の v5 が PHP 8.2 以上を要求していました。v4 なら 8.1 でも動くようです。依存ライブラリのメジャーバージョンを1つ下げれば、要求を 8.1 に緩められる余地があることになります。
下げるかどうかは、まだ決めていません。8.1 はすでにセキュリティ修正も終わっているバージョンなので、そこに合わせに行くのが利用者のためになるとも言い切れない。ただ、自分のプラグインが要求している数字が、自分で決めたものではなく依存から来ていたという事実は、知らないままにしておくべきではなかったと思います。
余談ですが、この日いちばん時間を使ったのは WP-CLI の phar を入れ直そうとしていた30分でした。壊れているのは自分の側だと思わないうちは、まわりの道具ばかり疑ってしまう。
次の自分に渡すメモ
Composer を同梱したプラグインを書くときは、vendor/autoload.php の require より前にバージョンを見る。足りなければ return して、管理画面には admin_notices、CLI には WP_CLI::warning。ヘッダの Requires PHP も書くが、あれは有効化のときにしか効かないので、コード側のガードと両方いる。
Xserver で WP-CLI が落ちたら、まず php -v を見る。サーバーパネルの設定は Web 側の話で、SSH は別物。5.4 が返ってくる事例ばかり見ていると、8.0 が返ってきたときに見逃す。
自分のプラグインが要求している PHP のバージョンは、依存ライブラリから来ていることがある。composer.json に書いた数字だけを見て決めた気になっていると、どこで決まったのか分からなくなる。
まだ確かめていないのは、8.1 まで下げる余地を実際に取るかどうかです。web-auth/webauthn-lib を v4 に落としたときに、いま使っている API がそのまま通るのかを見ていません。
入れたコード
プラグイン本体の先頭、vendor/autoload.php を require する手前に置いています。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | <?php /** * Plugin Name: Rapls Passkey Pro * Requires PHP: 8.2 * ... */ if ( ! defined( 'ABSPATH' ) ) { exit; } if ( PHP_VERSION_ID < 80200 ) { $message = sprintf( 'Rapls Passkey Pro is not running: PHP 8.2 or later is required, and this is PHP %s.', PHP_VERSION ); // 管理画面に出す add_action( 'admin_notices', static function () use ( $message ) { printf( '<div class="notice notice-error"><p>%s</p></div>', esc_html( $message ) ); } ); // WP-CLI に出す if ( defined( 'WP_CLI' ) && WP_CLI ) { WP_CLI::warning( $message ); } // ここから先は読み込まない return; } require_once __DIR__ . '/vendor/autoload.php'; |
プラグインファイルのトップレベルで return しているので、この下に書いた処理は一切走りません。vendor/autoload.php に到達しないということは、platform_check.php も走らない。例外が投げられる場所まで行かせないのが、この形の目的です。
関連記事
- WordPress.org に初めて出した自作プラグインが 2 回差し戻された話 ── プラグインを公開する側の、別の躓きの記録です。
- WP-Cron を wget から WP-CLI に乗り換えた話 ── 同じ Xserver で WP-CLI を動かしたときの記録です。
- 501 Not Implemented が出た|Xserver の WAF で WordPress が弾かれる ── サーバー側の設定で、書いたコードが通らなくなる話です。

コメント