Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
80b2114
Initial plan
Copilot Nov 7, 2025
0434a0f
Add error handling for require_once statements in core install methods
Copilot Nov 7, 2025
27758fe
Refactor error handling with require_upgrade_file helper method
Copilot Nov 7, 2025
a1b8628
Update src/Core_Command.php
swissspidy Nov 8, 2025
3e9d6c1
Update src/Core_Command.php
swissspidy Nov 8, 2025
16b95f2
Prevent shutdown handler from executing after successful require_once
Copilot Nov 8, 2025
e94788f
Simplify error handling - WP_CLI::error() is always available
Copilot Nov 8, 2025
f946f18
PHPStan fix
swissspidy Nov 10, 2025
015f95e
Merge branch 'main' into copilot/fix-core-install-error-255
swissspidy Dec 19, 2025
138666f
Merge branch 'main' into copilot/fix-core-install-error-255
swissspidy Jan 20, 2026
44b1daa
Remove E_COMPILE_WARNING from error types - it's non-fatal
Copilot Jan 20, 2026
62c21f6
Add Behat tests for error handling in core install
Copilot Feb 15, 2026
afcc087
Update test to simulate missing mysqli extension scenario
Copilot Feb 15, 2026
75bcd25
Merge branch 'main' into copilot/fix-core-install-error-255
swissspidy Feb 15, 2026
0314e92
Fix test to avoid PHP parse error when simulating missing extension
Copilot Feb 15, 2026
6fc1337
Improve error message to clarify WordPress core vs plugin/theme errors
Copilot Mar 11, 2026
bb95bbc
Merge branch 'main' into copilot/fix-core-install-error-255
swissspidy Mar 11, 2026
6079593
Apply suggestion from @swissspidy
swissspidy Mar 11, 2026
a00b72d
Lint fix
swissspidy Mar 11, 2026
de24fc3
Simplify error handling to work with WP-CLI's built-in shutdown handler
Copilot Mar 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions features/core-install.feature
Original file line number Diff line number Diff line change
Expand Up @@ -322,3 +322,69 @@ 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 from missing extension
Given an empty directory
And WP files
And wp-config.php
And a database

# 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 "<?php trigger_error('Call to undefined function mysqli_connect()', E_USER_ERROR);" > 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:
"""
Fatal error: Call to undefined function mysqli_connect()
"""
And STDERR should contain:
"""
Error: There has been a critical error on this website
"""
And the return code should be 255
38 changes: 31 additions & 7 deletions src/Core_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ function wp_new_blog_notification() {
add_filter( 'send_site_admin_email_change_email', '__return_false' );
}

require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$this->require_upgrade_file( 'WordPress installation' );

$defaults = [
'title' => '',
Expand Down Expand Up @@ -744,7 +744,7 @@ function wp_new_blog_notification() {
private function multisite_convert_( $assoc_args ) {
global $wpdb;

require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$this->require_upgrade_file( 'multisite conversion' );

$domain = self::get_clean_basedomain();
if ( 'localhost' === $domain && ! empty( $assoc_args['subdomains'] ) ) {
Expand Down Expand Up @@ -1263,7 +1263,7 @@ public function update( $args, $assoc_args ) {
|| Utils\get_flag_value( $assoc_args, 'force' )
|| ( $update->locale ?: 'en_US' ) !== ( self::get_wp_details()['wp_local_package'] ?: 'en_US' ) ) ) {

require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$this->require_upgrade_file( 'WordPress core update' );

// Prevent async translation updates which output HTML.
add_action(
Expand Down Expand Up @@ -1545,7 +1545,7 @@ 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';
$this->require_upgrade_file( 'WordPress database update' );

/**
* @var string $wp_current_db_version
Expand Down Expand Up @@ -1949,11 +1949,35 @@ function () use ( $new_zip_file ) {
}

/**
* Checks if a WP_Error is related to the core_updater.lock.
* 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 \WP_Error $error The error object to check.
* @return bool True if the error is related to the lock, false otherwise.
* @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." );
}

// phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- Path comes from WordPress itself.
require_once $upgrade_file;
}

/**
* 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() ) {
Expand Down
Loading