From 6558cacfd8f68b702b549857ced45b686810fdd0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 12 Feb 2026 22:43:24 +0000
Subject: [PATCH 1/6] Initial plan
From 1cd40e6e0c22b3d9ebaddb1c61aae63dd0f41a46 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 12 Feb 2026 22:48:21 +0000
Subject: [PATCH 2/6] Fix: Always activate plugin when --activate flag is used
with install
When using `wp plugin install --force --activate`, the plugin should
always be activated even if it appears to be already active. This
fixes an edge case where WordPress might deactivate a plugin during
the installation process (when plugin files are temporarily deleted),
leaving the plugin deactivated after the update completes.
The fix modifies the activate() method to skip the "already active"
check when called from the install command (chained_command = true),
ensuring that explicit activation requests are always honored.
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
---
features/plugin-install.feature | 33 +++++++++++++++++++++++++++++++++
src/Plugin_Command.php | 4 +++-
2 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/features/plugin-install.feature b/features/plugin-install.feature
index 319c0787a..716926f4b 100644
--- a/features/plugin-install.feature
+++ b/features/plugin-install.feature
@@ -305,3 +305,36 @@ Feature: Install WordPress plugins
"""
active
"""
+
+ Scenario: Force update an active plugin with --activate flag should keep it activated
+ Given a WP install
+
+ When I run `wp plugin install hello-dolly --activate`
+ Then STDOUT should contain:
+ """
+ Plugin 'hello-dolly' activated.
+ """
+ And the return code should be 0
+
+ When I run `wp plugin list --name=hello-dolly --field=status`
+ Then STDOUT should be:
+ """
+ active
+ """
+
+ When I run `wp plugin install hello-dolly --force --activate`
+ Then STDOUT should contain:
+ """
+ Plugin updated successfully
+ """
+ And STDOUT should contain:
+ """
+ Success: Installed 1 of 1 plugins.
+ """
+ And the return code should be 0
+
+ When I run `wp plugin list --name=hello-dolly --field=status`
+ Then STDOUT should be:
+ """
+ active
+ """
diff --git a/src/Plugin_Command.php b/src/Plugin_Command.php
index 5ce246aca..073f1c55c 100644
--- a/src/Plugin_Command.php
+++ b/src/Plugin_Command.php
@@ -383,7 +383,9 @@ public function activate( $args, $assoc_args = [] ) {
continue;
}
// Don't reactivate active plugins, but do let them become network-active.
- if ( ! $network_wide && 'active' === $status ) {
+ // However, when called from install command (chained_command), always attempt activation
+ // to handle edge cases where the plugin may have been deactivated during the install process.
+ if ( ! $network_wide && 'active' === $status && ! $this->chained_command ) {
WP_CLI::warning( "Plugin '{$plugin->name}' is already active." );
continue;
}
From 5305959244a9180af007bea199c3d603b0e0f7e1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 12 Feb 2026 22:49:34 +0000
Subject: [PATCH 3/6] Add tests for plugin activation with --force flag
Add comprehensive tests to verify that plugins remain activated when
using --force with --activate or --activate-network flags. These tests
cover both single-site and multisite scenarios.
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
---
features/plugin-install.feature | 33 +++++++++++++++++++++++++++++++++
src/Plugin_Command.php | 4 +++-
2 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/features/plugin-install.feature b/features/plugin-install.feature
index 716926f4b..accd09f12 100644
--- a/features/plugin-install.feature
+++ b/features/plugin-install.feature
@@ -338,3 +338,36 @@ Feature: Install WordPress plugins
"""
active
"""
+
+ Scenario: Force update a network-active plugin with --activate-network flag should keep it network-activated
+ Given a WP multisite install
+
+ When I run `wp plugin install hello-dolly --activate-network`
+ Then STDOUT should contain:
+ """
+ Plugin 'hello-dolly' network activated.
+ """
+ And the return code should be 0
+
+ When I run `wp plugin list --name=hello-dolly --field=status`
+ Then STDOUT should be:
+ """
+ active-network
+ """
+
+ When I run `wp plugin install hello-dolly --force --activate-network`
+ Then STDOUT should contain:
+ """
+ Plugin updated successfully
+ """
+ And STDOUT should contain:
+ """
+ Success: Installed 1 of 1 plugins.
+ """
+ And the return code should be 0
+
+ When I run `wp plugin list --name=hello-dolly --field=status`
+ Then STDOUT should be:
+ """
+ active-network
+ """
diff --git a/src/Plugin_Command.php b/src/Plugin_Command.php
index 073f1c55c..d66247d2a 100644
--- a/src/Plugin_Command.php
+++ b/src/Plugin_Command.php
@@ -378,7 +378,9 @@ public function activate( $args, $assoc_args = [] ) {
continue;
}
// Network-active is the highest level of activation status.
- if ( 'active-network' === $status ) {
+ // However, when called from install command (chained_command), always attempt activation
+ // to handle edge cases where the plugin may have been deactivated during the install process.
+ if ( 'active-network' === $status && ! $this->chained_command ) {
WP_CLI::warning( "Plugin '{$plugin->name}' is already network active." );
continue;
}
From 1b639da63c8bb4c3f18370703e509fdf10d38267 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 17 Feb 2026 13:01:54 +0000
Subject: [PATCH 4/6] Fix activation error handling when called from install
When activate() is called from install command and the plugin is
already active, activate_plugin() returns WP_Error('plugin_already_active').
This should be treated as success, not failure, since the desired end
state (active plugin) is achieved.
Changes:
- Restructure activation checks to always run force deactivation first
- When chained_command is true and plugin is already active without force,
fall through to activate_plugin() and handle 'plugin_already_active' error
- Treat 'plugin_already_active' as success when called from install command
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
---
src/Plugin_Command.php | 50 +++++++++++++++++++++++-------------------
1 file changed, 28 insertions(+), 22 deletions(-)
diff --git a/src/Plugin_Command.php b/src/Plugin_Command.php
index 428ebc651..9f6ced786 100644
--- a/src/Plugin_Command.php
+++ b/src/Plugin_Command.php
@@ -387,25 +387,23 @@ public function activate( $args, $assoc_args = [] ) {
continue;
}
// Network-active is the highest level of activation status.
- // However, when called from install command (chained_command), always attempt activation
- // to handle edge cases where the plugin may have been deactivated during the install process.
- if ( 'active-network' === $status && ! $this->chained_command ) {
+ if ( 'active-network' === $status ) {
// If force flag is set, deactivate and reactivate to run activation hooks.
if ( $force ) {
deactivate_plugins( $plugin->file, false, true );
- } else {
+ } elseif ( ! $this->chained_command ) {
+ // Only skip if not called from install command.
WP_CLI::warning( "Plugin '{$plugin->name}' is already network active." );
continue;
}
}
// Don't reactivate active plugins, but do let them become network-active.
- // However, when called from install command (chained_command), always attempt activation
- // to handle edge cases where the plugin may have been deactivated during the install process.
- if ( ! $network_wide && 'active' === $status && ! $this->chained_command ) {
+ if ( ! $network_wide && 'active' === $status ) {
// If force flag is set, deactivate and reactivate to run activation hooks.
if ( $force ) {
deactivate_plugins( $plugin->file, false, false );
- } else {
+ } elseif ( ! $this->chained_command ) {
+ // Only skip if not called from install command.
WP_CLI::warning( "Plugin '{$plugin->name}' is already active." );
continue;
}
@@ -419,22 +417,30 @@ public function activate( $args, $assoc_args = [] ) {
$result = activate_plugin( $plugin->file, '', $network_wide );
if ( is_wp_error( $result ) ) {
- $message = $result->get_error_message();
- $message = (string) preg_replace( '/]+>.*<\/a>/im', '', $message );
- $message = wp_strip_all_tags( $message );
- $message = str_replace( 'Error: ', '', $message );
- WP_CLI::warning( "Failed to activate plugin. {$message}" );
- // If the error is due to unexpected output, display it for debugging
- if ( 'unexpected_output' === $result->get_error_code() ) {
- /**
- * @var string $output
- */
- $output = $result->get_error_data();
- if ( ! empty( $output ) ) {
- WP_CLI::debug( "Unexpected output: {$output}", 'plugin' );
+ // When called from install command, treat 'already_active' as success.
+ // This handles race conditions where WordPress may have preserved activation
+ // status during the install process.
+ if ( $this->chained_command && 'plugin_already_active' === $result->get_error_code() ) {
+ $this->active_output( $plugin->name, $plugin->file, $network_wide, 'activate' );
+ ++$successes;
+ } else {
+ $message = $result->get_error_message();
+ $message = (string) preg_replace( '/]+>.*<\/a>/im', '', $message );
+ $message = wp_strip_all_tags( $message );
+ $message = str_replace( 'Error: ', '', $message );
+ WP_CLI::warning( "Failed to activate plugin. {$message}" );
+ // If the error is due to unexpected output, display it for debugging
+ if ( 'unexpected_output' === $result->get_error_code() ) {
+ /**
+ * @var string $output
+ */
+ $output = $result->get_error_data();
+ if ( ! empty( $output ) ) {
+ WP_CLI::debug( "Unexpected output: {$output}", 'plugin' );
+ }
}
+ ++$errors;
}
- ++$errors;
} else {
$this->active_output( $plugin->name, $plugin->file, $network_wide, 'activate' );
++$successes;
From c13bbd5e941a3a857baa1ebc14bf8ee920d90adc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 17 Feb 2026 13:03:15 +0000
Subject: [PATCH 5/6] Update tests to exercise the actual fix
Replace --force tests with tests that actually exercise the code path
where activate_plugin() returns 'plugin_already_active'. These tests
verify that installing an already-active plugin with --activate (without
--force) correctly treats the 'plugin_already_active' error as success.
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
---
features/plugin-install.feature | 28 ++++++++++++++++++----------
1 file changed, 18 insertions(+), 10 deletions(-)
diff --git a/features/plugin-install.feature b/features/plugin-install.feature
index accd09f12..7eae2b28f 100644
--- a/features/plugin-install.feature
+++ b/features/plugin-install.feature
@@ -306,7 +306,7 @@ Feature: Install WordPress plugins
active
"""
- Scenario: Force update an active plugin with --activate flag should keep it activated
+ Scenario: Install with --activate on already-active plugin should keep it activated
Given a WP install
When I run `wp plugin install hello-dolly --activate`
@@ -322,14 +322,18 @@ Feature: Install WordPress plugins
active
"""
- When I run `wp plugin install hello-dolly --force --activate`
- Then STDOUT should contain:
+ When I try `wp plugin install hello-dolly --activate`
+ Then STDERR should contain:
+ """
+ Warning: hello-dolly: Plugin already installed.
"""
- Plugin updated successfully
+ And STDOUT should contain:
+ """
+ Activating 'hello-dolly'...
"""
And STDOUT should contain:
"""
- Success: Installed 1 of 1 plugins.
+ Plugin 'hello-dolly' activated.
"""
And the return code should be 0
@@ -339,7 +343,7 @@ Feature: Install WordPress plugins
active
"""
- Scenario: Force update a network-active plugin with --activate-network flag should keep it network-activated
+ Scenario: Install with --activate-network on already-network-active plugin should keep it activated
Given a WP multisite install
When I run `wp plugin install hello-dolly --activate-network`
@@ -355,14 +359,18 @@ Feature: Install WordPress plugins
active-network
"""
- When I run `wp plugin install hello-dolly --force --activate-network`
- Then STDOUT should contain:
+ When I try `wp plugin install hello-dolly --activate-network`
+ Then STDERR should contain:
+ """
+ Warning: hello-dolly: Plugin already installed.
"""
- Plugin updated successfully
+ And STDOUT should contain:
+ """
+ Network-activating 'hello-dolly'...
"""
And STDOUT should contain:
"""
- Success: Installed 1 of 1 plugins.
+ Plugin 'hello-dolly' network activated.
"""
And the return code should be 0
From 6a597e472df141cdf412778d4c8d82eaa9e600b7 Mon Sep 17 00:00:00 2001
From: Pascal Birchler
Date: Tue, 17 Feb 2026 15:16:12 +0100
Subject: [PATCH 6/6] Apply suggestions from code review
---
src/Plugin_Command.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Plugin_Command.php b/src/Plugin_Command.php
index 9f6ced786..68cb819b1 100644
--- a/src/Plugin_Command.php
+++ b/src/Plugin_Command.php
@@ -392,7 +392,7 @@ public function activate( $args, $assoc_args = [] ) {
if ( $force ) {
deactivate_plugins( $plugin->file, false, true );
} elseif ( ! $this->chained_command ) {
- // Only skip if not called from install command.
+ // Only skip if not part of a chained command.
WP_CLI::warning( "Plugin '{$plugin->name}' is already network active." );
continue;
}
@@ -403,7 +403,7 @@ public function activate( $args, $assoc_args = [] ) {
if ( $force ) {
deactivate_plugins( $plugin->file, false, false );
} elseif ( ! $this->chained_command ) {
- // Only skip if not called from install command.
+ // Only skip if not part of a chained command.
WP_CLI::warning( "Plugin '{$plugin->name}' is already active." );
continue;
}
@@ -417,7 +417,7 @@ public function activate( $args, $assoc_args = [] ) {
$result = activate_plugin( $plugin->file, '', $network_wide );
if ( is_wp_error( $result ) ) {
- // When called from install command, treat 'already_active' as success.
+ // When called from a chained command, treat 'already_active' as success.
// This handles race conditions where WordPress may have preserved activation
// status during the install process.
if ( $this->chained_command && 'plugin_already_active' === $result->get_error_code() ) {