From 80b211457ee9f46555f062a611cdcaa8f683702d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 22:11:39 +0000 Subject: [PATCH 01/16] Initial plan From 0434a0f27e10427597963b000babf84594d7de70 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 22:17:25 +0000 Subject: [PATCH 02/16] Add error handling for require_once statements in core install methods Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Core_Command.php | 64 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index b9bbb2b4..3c88b4aa 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -659,7 +659,21 @@ function wp_new_blog_notification() { add_filter( 'send_site_admin_email_change_email', '__return_false' ); } - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; + if ( ! file_exists( $upgrade_file ) ) { + WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); + } + + // Suppress errors from require_once and catch them for better error messages. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. + if ( ! @require_once $upgrade_file ) { + $error = error_get_last(); + if ( $error ) { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); + } else { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); + } + } $defaults = [ 'title' => '', @@ -715,7 +729,21 @@ function wp_new_blog_notification() { private function multisite_convert_( $assoc_args ) { global $wpdb; - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; + if ( ! file_exists( $upgrade_file ) ) { + WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); + } + + // Suppress errors from require_once and catch them for better error messages. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. + if ( ! @require_once $upgrade_file ) { + $error = error_get_last(); + if ( $error ) { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); + } else { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); + } + } $domain = self::get_clean_basedomain(); if ( 'localhost' === $domain && ! empty( $assoc_args['subdomains'] ) ) { @@ -1211,7 +1239,21 @@ public function update( $args, $assoc_args ) { && ( $update->version !== $wp_version || Utils\get_flag_value( $assoc_args, 'force' ) ) ) { - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; + if ( ! file_exists( $upgrade_file ) ) { + WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); + } + + // Suppress errors from require_once and catch them for better error messages. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. + if ( ! @require_once $upgrade_file ) { + $error = error_get_last(); + if ( $error ) { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); + } else { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); + } + } if ( $update->version ) { WP_CLI::log( "Updating to version {$update->version} ({$update->locale})..." ); @@ -1371,7 +1413,21 @@ public function update_db( $args, $assoc_args ) { } WP_CLI::success( "WordPress database upgraded on {$success}/{$total} sites." ); } else { - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; + if ( ! file_exists( $upgrade_file ) ) { + WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); + } + + // Suppress errors from require_once and catch them for better error messages. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. + if ( ! @require_once $upgrade_file ) { + $error = error_get_last(); + if ( $error ) { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); + } else { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); + } + } /** * @var string $wp_current_db_version From 27758fe93b4bb51939e4c2fc5f2c5025e1896b86 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 22:23:59 +0000 Subject: [PATCH 03/16] Refactor error handling with require_upgrade_file helper method Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Core_Command.php | 109 +++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 60 deletions(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index 3c88b4aa..f902bf02 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -659,21 +659,7 @@ function wp_new_blog_notification() { add_filter( 'send_site_admin_email_change_email', '__return_false' ); } - $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; - if ( ! file_exists( $upgrade_file ) ) { - WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); - } - - // Suppress errors from require_once and catch them for better error messages. - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. - if ( ! @require_once $upgrade_file ) { - $error = error_get_last(); - if ( $error ) { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); - } else { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); - } - } + $this->require_upgrade_file( 'WordPress installation' ); $defaults = [ 'title' => '', @@ -729,21 +715,7 @@ function wp_new_blog_notification() { private function multisite_convert_( $assoc_args ) { global $wpdb; - $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; - if ( ! file_exists( $upgrade_file ) ) { - WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); - } - - // Suppress errors from require_once and catch them for better error messages. - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. - if ( ! @require_once $upgrade_file ) { - $error = error_get_last(); - if ( $error ) { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); - } else { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); - } - } + $this->require_upgrade_file( 'multisite conversion' ); $domain = self::get_clean_basedomain(); if ( 'localhost' === $domain && ! empty( $assoc_args['subdomains'] ) ) { @@ -1239,21 +1211,7 @@ public function update( $args, $assoc_args ) { && ( $update->version !== $wp_version || Utils\get_flag_value( $assoc_args, 'force' ) ) ) { - $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; - if ( ! file_exists( $upgrade_file ) ) { - WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); - } - - // Suppress errors from require_once and catch them for better error messages. - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. - if ( ! @require_once $upgrade_file ) { - $error = error_get_last(); - if ( $error ) { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); - } else { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); - } - } + $this->require_upgrade_file( 'WordPress core update' ); if ( $update->version ) { WP_CLI::log( "Updating to version {$update->version} ({$update->locale})..." ); @@ -1413,21 +1371,7 @@ public function update_db( $args, $assoc_args ) { } WP_CLI::success( "WordPress database upgraded on {$success}/{$total} sites." ); } else { - $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; - if ( ! file_exists( $upgrade_file ) ) { - WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); - } - - // Suppress errors from require_once and catch them for better error messages. - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. - if ( ! @require_once $upgrade_file ) { - $error = error_get_last(); - if ( $error ) { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); - } else { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); - } - } + $this->require_upgrade_file( 'WordPress database update' ); /** * @var string $wp_current_db_version @@ -1719,4 +1663,49 @@ function () use ( $new_zip_file ) { WP_CLI::error( 'ZipArchive failed to open ZIP file.' ); } } + + /** + * Safely requires the WordPress upgrade.php file with error handling. + * + * This method checks for file existence and readability before requiring, + * and registers a shutdown function to catch fatal errors during file loading + * (e.g., missing PHP extensions or other runtime issues). + * + * @param string $context Context for error messages (e.g., 'installation', 'upgrade', 'database update'). + */ + private function require_upgrade_file( $context = 'WordPress operation' ) { + $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; + + if ( ! file_exists( $upgrade_file ) ) { + WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); + } + + if ( ! is_readable( $upgrade_file ) ) { + WP_CLI::error( "Cannot read WordPress installation file '{$upgrade_file}'. Check file permissions." ); + } + + // Register a shutdown function to catch fatal errors during require_once. + $shutdown_handler = function () use ( $upgrade_file, $context ) { + $error = error_get_last(); + if ( null !== $error && in_array( $error['type'], [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR ], true ) ) { + // Check if error occurred in the upgrade file or files it includes. + if ( false !== strpos( $error['file'], 'wp-admin/includes/' ) || false !== strpos( $error['file'], 'wp-includes/' ) ) { + WP_CLI::error( + sprintf( + "Failed to load WordPress files for %s. This often indicates a missing PHP extension or a corrupted WordPress installation.\n\nError: %s in %s on line %d\n\nPlease check that all required PHP extensions are installed and that your WordPress installation is complete.", + $context, + $error['message'], + $error['file'], + $error['line'] + ) + ); + } + } + }; + + register_shutdown_function( $shutdown_handler ); + + // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- Path comes from WordPress itself. + require_once $upgrade_file; + } } From a1b8628ae72eb55431983ebe729e6160253a984a Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 8 Nov 2025 11:13:13 +0100 Subject: [PATCH 04/16] Update src/Core_Command.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Core_Command.php | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index f902bf02..f616b306 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1687,18 +1687,42 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { // Register a shutdown function to catch fatal errors during require_once. $shutdown_handler = function () use ( $upgrade_file, $context ) { $error = error_get_last(); - if ( null !== $error && in_array( $error['type'], [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR ], true ) ) { + if ( + null !== $error + && in_array( + $error['type'], + [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_COMPILE_WARNING, E_USER_ERROR ], + true + ) + ) { // Check if error occurred in the upgrade file or files it includes. - if ( false !== strpos( $error['file'], 'wp-admin/includes/' ) || false !== strpos( $error['file'], 'wp-includes/' ) ) { - WP_CLI::error( - sprintf( - "Failed to load WordPress files for %s. This often indicates a missing PHP extension or a corrupted WordPress installation.\n\nError: %s in %s on line %d\n\nPlease check that all required PHP extensions are installed and that your WordPress installation is complete.", - $context, - $error['message'], - $error['file'], - $error['line'] - ) + if ( + false !== strpos( $error['file'], 'wp-admin/includes/' ) + || false !== strpos( $error['file'], 'wp-includes/' ) + ) { + $message = sprintf( + "Failed to load WordPress files for %s. This often indicates a missing PHP extension or a corrupted WordPress installation.\n\nError: %s in %s on line %d\n\nPlease check that all required PHP extensions are installed and that your WordPress installation is complete.", + $context, + $error['message'], + $error['file'], + $error['line'] ); + // Attempt to use WP_CLI::error(), but fall back to direct output if in shutdown. + try { + if ( class_exists( 'WP_CLI' ) && method_exists( 'WP_CLI', 'error' ) ) { + WP_CLI::error( $message ); + } else { + throw new \Exception( 'WP_CLI::error() not available' ); + } + } catch ( \Throwable $e ) { + // Fallback: output directly to STDERR and exit. + if ( defined( 'STDERR' ) ) { + fwrite( STDERR, $message . "\n" ); + } else { + error_log( $message ); + } + exit( 1 ); + } } } }; From 3e9d6c1ee1828f43d56ea810bf4257ac2bfdb732 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 8 Nov 2025 11:13:39 +0100 Subject: [PATCH 05/16] Update src/Core_Command.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Core_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index f616b306..4f7dd795 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1685,7 +1685,7 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { } // Register a shutdown function to catch fatal errors during require_once. - $shutdown_handler = function () use ( $upgrade_file, $context ) { + $shutdown_handler = function () use ( $context ) { $error = error_get_last(); if ( null !== $error From 16b95f243d0081646dd869d22cfb2422cb8c25c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 10:16:17 +0000 Subject: [PATCH 06/16] Prevent shutdown handler from executing after successful require_once Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Core_Command.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index 4f7dd795..34367558 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1684,8 +1684,16 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { WP_CLI::error( "Cannot read WordPress installation file '{$upgrade_file}'. Check file permissions." ); } + // Use a flag to track successful completion and prevent handler from executing after success. + $require_completed = false; + // Register a shutdown function to catch fatal errors during require_once. - $shutdown_handler = function () use ( $context ) { + $shutdown_handler = function () use ( $context, &$require_completed ) { + // Only handle errors if require_once did not complete successfully. + if ( $require_completed ) { + return; + } + $error = error_get_last(); if ( null !== $error @@ -1731,5 +1739,8 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- Path comes from WordPress itself. require_once $upgrade_file; + + // Mark as completed to prevent the shutdown handler from executing on unrelated errors. + $require_completed = true; } } From e94788f5ca585ca780a7c2d3df7d7ad50ccfbf85 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 10:28:56 +0000 Subject: [PATCH 07/16] Simplify error handling - WP_CLI::error() is always available Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Core_Command.php | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index 34367558..066953cf 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1708,29 +1708,15 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { false !== strpos( $error['file'], 'wp-admin/includes/' ) || false !== strpos( $error['file'], 'wp-includes/' ) ) { - $message = sprintf( - "Failed to load WordPress files for %s. This often indicates a missing PHP extension or a corrupted WordPress installation.\n\nError: %s in %s on line %d\n\nPlease check that all required PHP extensions are installed and that your WordPress installation is complete.", - $context, - $error['message'], - $error['file'], - $error['line'] + WP_CLI::error( + sprintf( + "Failed to load WordPress files for %s. This often indicates a missing PHP extension or a corrupted WordPress installation.\n\nError: %s in %s on line %d\n\nPlease check that all required PHP extensions are installed and that your WordPress installation is complete.", + $context, + $error['message'], + $error['file'], + $error['line'] + ) ); - // Attempt to use WP_CLI::error(), but fall back to direct output if in shutdown. - try { - if ( class_exists( 'WP_CLI' ) && method_exists( 'WP_CLI', 'error' ) ) { - WP_CLI::error( $message ); - } else { - throw new \Exception( 'WP_CLI::error() not available' ); - } - } catch ( \Throwable $e ) { - // Fallback: output directly to STDERR and exit. - if ( defined( 'STDERR' ) ) { - fwrite( STDERR, $message . "\n" ); - } else { - error_log( $message ); - } - exit( 1 ); - } } } }; From f946f1834d48028f740d7797a9afd5ae1ef2731c Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 10 Nov 2025 16:02:13 +0100 Subject: [PATCH 08/16] PHPStan fix --- src/Core_Command.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Core_Command.php b/src/Core_Command.php index 066953cf..dfa68dcf 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1690,6 +1690,7 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { // Register a shutdown function to catch fatal errors during require_once. $shutdown_handler = function () use ( $context, &$require_completed ) { // Only handle errors if require_once did not complete successfully. + // @phpstan-ignore-next-line if ( $require_completed ) { return; } From 44b1daad46768a594717297bc8fc8ab3e43df951 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:24:27 +0000 Subject: [PATCH 09/16] Remove E_COMPILE_WARNING from error types - it's non-fatal Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Core_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index 5a5dab8b..a49aafe9 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1709,7 +1709,7 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { null !== $error && in_array( $error['type'], - [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_COMPILE_WARNING, E_USER_ERROR ], + [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR ], true ) ) { From 62c21f60288a9b63b2a92f7a4a65485ab5826613 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 21:31:08 +0000 Subject: [PATCH 10/16] Add Behat tests for error handling in core install Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/core-install.feature | 64 +++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/features/core-install.feature b/features/core-install.feature index 4b3b74c5..bfa5e722 100644 --- a/features/core-install.feature +++ b/features/core-install.feature @@ -351,3 +351,67 @@ Feature: Install WordPress core """ Success: Switched to 'Rockfield' theme. """ + + Scenario: Core install should provide helpful error when upgrade.php is missing + Given an empty directory + And WP files + And wp-config.php + And a database + + When I run `rm wp-admin/includes/upgrade.php` + Then the return code should be 0 + + When I try `wp core install --url=example.org --title=Test --admin_user=testadmin --admin_email=testadmin@example.com --admin_password=testpass` + Then STDERR should contain: + """ + Error: WordPress installation is incomplete. The file + """ + And STDERR should contain: + """ + wp-admin/includes/upgrade.php' is missing. + """ + And the return code should be 1 + + Scenario: Core install should provide helpful error when upgrade.php is not readable + Given an empty directory + And WP files + And wp-config.php + And a database + + When I run `chmod 000 wp-admin/includes/upgrade.php` + Then the return code should be 0 + + When I try `wp core install --url=example.org --title=Test --admin_user=testadmin --admin_email=testadmin@example.com --admin_password=testpass` + Then STDERR should contain: + """ + Error: Cannot read WordPress installation file + """ + And STDERR should contain: + """ + wp-admin/includes/upgrade.php'. Check file permissions. + """ + And the return code should be 1 + + When I run `chmod 644 wp-admin/includes/upgrade.php` + Then the return code should be 0 + + Scenario: Core install should provide helpful error when WordPress file has fatal error + Given an empty directory + And WP files + And wp-config.php + And a database + + # Inject a fatal error into a WordPress core file that will be loaded during install + When I run `echo " wp-admin/includes/upgrade.php` + Then the return code should be 0 + + When I try `wp core install --url=example.org --title=Test --admin_user=testadmin --admin_email=testadmin@example.com --admin_password=testpass` + Then STDERR should contain: + """ + Error: Failed to load WordPress files for WordPress installation + """ + And STDERR should contain: + """ + This often indicates a missing PHP extension or a corrupted WordPress installation + """ + And the return code should be 1 From afcc0878f6aebdd0344d3b8a257419e94bfacaae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 21:56:27 +0000 Subject: [PATCH 11/16] Update test to simulate missing mysqli extension scenario Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/core-install.feature | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/features/core-install.feature b/features/core-install.feature index bfa5e722..fdebf672 100644 --- a/features/core-install.feature +++ b/features/core-install.feature @@ -395,14 +395,26 @@ Feature: Install WordPress core When I run `chmod 644 wp-admin/includes/upgrade.php` Then the return code should be 0 - Scenario: Core install should provide helpful error when WordPress file has fatal error + Scenario: Core install should provide helpful error when WordPress file has fatal error from missing extension Given an empty directory And WP files And wp-config.php And a database - # Inject a fatal error into a WordPress core file that will be loaded during install - When I run `echo " wp-admin/includes/upgrade.php` + # Simulate a missing mysqli extension by injecting a fatal error that mimics + # what happens when mysqli_connect is called but the extension is not available + # This reproduces the original bug report scenario + When I run `echo " wp-includes/mysqli-check.php` + Then the return code should be 0 + + # Inject this check at the beginning of upgrade.php to trigger the error during install + When I run `echo " wp-admin/includes/upgrade-new.php` + Then the return code should be 0 + + When I run `cat wp-admin/includes/upgrade.php >> wp-admin/includes/upgrade-new.php` + Then the return code should be 0 + + When I run `mv wp-admin/includes/upgrade-new.php wp-admin/includes/upgrade.php` Then the return code should be 0 When I try `wp core install --url=example.org --title=Test --admin_user=testadmin --admin_email=testadmin@example.com --admin_password=testpass` @@ -414,4 +426,8 @@ Feature: Install WordPress core """ This often indicates a missing PHP extension or a corrupted WordPress installation """ + And STDERR should contain: + """ + Call to undefined function mysqli_connect() + """ And the return code should be 1 From 0314e928f5a453c2d0be4300829539a6816ba22c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 22:34:24 +0000 Subject: [PATCH 12/16] Fix test to avoid PHP parse error when simulating missing extension Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/core-install.feature | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/features/core-install.feature b/features/core-install.feature index fdebf672..2e1e628d 100644 --- a/features/core-install.feature +++ b/features/core-install.feature @@ -401,20 +401,10 @@ Feature: Install WordPress core And wp-config.php And a database - # Simulate a missing mysqli extension by injecting a fatal error that mimics - # what happens when mysqli_connect is called but the extension is not available - # This reproduces the original bug report scenario - When I run `echo " wp-includes/mysqli-check.php` - Then the return code should be 0 - - # Inject this check at the beginning of upgrade.php to trigger the error during install - When I run `echo " wp-admin/includes/upgrade-new.php` - Then the return code should be 0 - - When I run `cat wp-admin/includes/upgrade.php >> wp-admin/includes/upgrade-new.php` - Then the return code should be 0 - - When I run `mv wp-admin/includes/upgrade-new.php wp-admin/includes/upgrade.php` + # Simulate a missing mysqli extension by replacing upgrade.php with code that + # triggers a fatal error mimicking what happens when mysqli_connect is called + # but the extension is not available. This reproduces the original bug report scenario + When I run `echo " wp-admin/includes/upgrade.php` Then the return code should be 0 When I try `wp core install --url=example.org --title=Test --admin_user=testadmin --admin_email=testadmin@example.com --admin_password=testpass` From 6fc13378eb17c7892707c2f433a4930f2b694ea4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:19:41 +0000 Subject: [PATCH 13/16] Improve error message to clarify WordPress core vs plugin/theme errors Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/core-install.feature | 6 +++++- src/Core_Command.php | 20 ++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/features/core-install.feature b/features/core-install.feature index 2e1e628d..470815b5 100644 --- a/features/core-install.feature +++ b/features/core-install.feature @@ -414,7 +414,11 @@ Feature: Install WordPress core """ And STDERR should contain: """ - This often indicates a missing PHP extension or a corrupted WordPress installation + This error is in WordPress core files, not a plugin or theme + """ + And STDERR should contain: + """ + It often indicates a missing PHP extension (like mysqli) """ And STDERR should contain: """ diff --git a/src/Core_Command.php b/src/Core_Command.php index 5d6aa47d..18b6a0d2 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1853,15 +1853,19 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { false !== strpos( $error['file'], 'wp-admin/includes/' ) || false !== strpos( $error['file'], 'wp-includes/' ) ) { - WP_CLI::error( - sprintf( - "Failed to load WordPress files for %s. This often indicates a missing PHP extension or a corrupted WordPress installation.\n\nError: %s in %s on line %d\n\nPlease check that all required PHP extensions are installed and that your WordPress installation is complete.", - $context, - $error['message'], - $error['file'], - $error['line'] - ) + // Provide a more specific error message for WordPress core file errors + // before WP-CLI's generic shutdown handler runs + $message = sprintf( + "Failed to load WordPress files for %s.\n\nError: %s in %s on line %d\n\nThis error is in WordPress core files, not a plugin or theme.\nIt often indicates a missing PHP extension (like mysqli) or corrupted WordPress installation.\n\nPlease check that all required PHP extensions are installed and that your WordPress installation is complete.", + $context, + $error['message'], + $error['file'], + $error['line'] ); + + // Output directly and exit to avoid WP-CLI's generic plugin/theme suggestion + fwrite( STDERR, "Error: {$message}\n" ); + exit( 1 ); } } }; From 6079593ac367d05503e4d96f50f36f7069ec7a3b Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 11 Mar 2026 22:38:36 +0100 Subject: [PATCH 14/16] Apply suggestion from @swissspidy --- features/core-install.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/core-install.feature b/features/core-install.feature index 2bfbc6a5..c12a2262 100644 --- a/features/core-install.feature +++ b/features/core-install.feature @@ -395,4 +395,4 @@ Feature: Install WordPress core """ Call to undefined function mysqli_connect() """ - And the return code should be 1 + And the return code should be 255 From a00b72d6b71cd33b94d872d38f18581d1207b0fa Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 11 Mar 2026 22:47:24 +0100 Subject: [PATCH 15/16] Lint fix --- src/Core_Command.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index bdd5110a..93bab24a 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -2002,7 +2002,7 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { $error['file'], $error['line'] ); - + // Output directly and exit to avoid WP-CLI's generic plugin/theme suggestion fwrite( STDERR, "Error: {$message}\n" ); exit( 1 ); @@ -2017,14 +2017,14 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { // Mark as completed to prevent the shutdown handler from executing on unrelated errors. $require_completed = true; - } - - /** - * Checks if a WP_Error is related to the core_updater.lock. - * - * @param \WP_Error $error The error object to check. - * @return bool True if the error is related to the lock, false otherwise. - */ + } + + /** + * Checks if a WP_Error is related to the core_updater.lock. + * + * @param \WP_Error $error The error object to check. + * @return bool True if the error is related to the lock, false otherwise. + */ private static function is_lock_error( $error ) { // Check for the 'locked' error code used by WordPress Core if ( 'locked' === $error->get_error_code() ) { From de24fc3fa6d6af5151af7e314d4294543948d6d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 21:51:24 +0000 Subject: [PATCH 16/16] Simplify error handling to work with WP-CLI's built-in shutdown handler Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/core-install.feature | 12 ++------- src/Core_Command.php | 47 ----------------------------------- 2 files changed, 2 insertions(+), 57 deletions(-) diff --git a/features/core-install.feature b/features/core-install.feature index c12a2262..09d62323 100644 --- a/features/core-install.feature +++ b/features/core-install.feature @@ -381,18 +381,10 @@ Feature: Install WordPress core When I try `wp core install --url=example.org --title=Test --admin_user=testadmin --admin_email=testadmin@example.com --admin_password=testpass` Then STDERR should contain: """ - Error: Failed to load WordPress files for WordPress installation + Fatal error: Call to undefined function mysqli_connect() """ And STDERR should contain: """ - This error is in WordPress core files, not a plugin or theme - """ - And STDERR should contain: - """ - It often indicates a missing PHP extension (like mysqli) - """ - And STDERR should contain: - """ - Call to undefined function mysqli_connect() + Error: There has been a critical error on this website """ And the return code should be 255 diff --git a/src/Core_Command.php b/src/Core_Command.php index 93bab24a..5b480ed5 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1968,55 +1968,8 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { WP_CLI::error( "Cannot read WordPress installation file '{$upgrade_file}'. Check file permissions." ); } - // Use a flag to track successful completion and prevent handler from executing after success. - $require_completed = false; - - // Register a shutdown function to catch fatal errors during require_once. - $shutdown_handler = function () use ( $context, &$require_completed ) { - // Only handle errors if require_once did not complete successfully. - // @phpstan-ignore-next-line - if ( $require_completed ) { - return; - } - - $error = error_get_last(); - if ( - null !== $error - && in_array( - $error['type'], - [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR ], - true - ) - ) { - // Check if error occurred in the upgrade file or files it includes. - if ( - false !== strpos( $error['file'], 'wp-admin/includes/' ) - || false !== strpos( $error['file'], 'wp-includes/' ) - ) { - // Provide a more specific error message for WordPress core file errors - // before WP-CLI's generic shutdown handler runs - $message = sprintf( - "Failed to load WordPress files for %s.\n\nError: %s in %s on line %d\n\nThis error is in WordPress core files, not a plugin or theme.\nIt often indicates a missing PHP extension (like mysqli) or corrupted WordPress installation.\n\nPlease check that all required PHP extensions are installed and that your WordPress installation is complete.", - $context, - $error['message'], - $error['file'], - $error['line'] - ); - - // Output directly and exit to avoid WP-CLI's generic plugin/theme suggestion - fwrite( STDERR, "Error: {$message}\n" ); - exit( 1 ); - } - } - }; - - register_shutdown_function( $shutdown_handler ); - // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- Path comes from WordPress itself. require_once $upgrade_file; - - // Mark as completed to prevent the shutdown handler from executing on unrelated errors. - $require_completed = true; } /**