Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Feb 3, 2026

On Windows PowerShell, command substitution returns space-separated values as a single string rather than individual arguments. This breaks commands expecting multiple IDs:

# PowerShell passes "1164 1186" as one string argument
wp post delete $(wp post list --post_type='page' --format=ids)
# Result: Only post 1164 deleted (cast to int drops " 1186")

Changes

  • Runner.php: Added Windows-specific argument splitting in back_compat_conversions()

    • Detects space-separated numeric patterns using /^\d+(\s+\d+)+$/
    • Splits matched arguments on whitespace
    • Preserves non-matching arguments unchanged (non-numeric strings, single IDs, leading/trailing spaces)
  • Tests: Added unit tests covering splitting behavior and edge cases

Implementation

if ( Utils\is_windows() ) {
    $split_args = [];
    foreach ( $args as $arg ) {
        if ( is_string( $arg ) && preg_match( '/^\d+(\s+\d+)+$/', $arg ) ) {
            $ids = preg_split( '/\s+/', $arg, -1, PREG_SPLIT_NO_EMPTY );
            if ( false !== $ids ) {
                $split_args = array_merge( $split_args, $ids );
            }
        } else {
            $split_args[] = $arg;
        }
    }
    $args = $split_args;
}

This mirrors Unix shell argument expansion behavior without affecting other platforms or non-ID arguments.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • example.com
    • Triggering command: /usr/bin/php php vendor/bin/phpunit --color=always --bootstrap ./vendor/wp-cli/wp-cli-tests/tests/bootstrap.php k/wp-cli/wp-cli/php/commands/src/CLI_Cache_Command.php k/wp-cli/wp-cli/php/commands/src/Help_Command.php ndor/bin/git k/wp-cli/wp-cli/git on.php k/wp-cli/wp-cli/-a Extension.php nFin�� ionsIterator.php-v nArrayIterator.php sh iner/Specificatigit nIterator.php ificationLocator-v grep (dns block)
    • Triggering command: /usr/bin/php php vendor/bin/phpunit --color=always --bootstrap ./vendor/wp-cli/wp-cli-tests/tests/bootstrap.php (dns block)
  • nosuchhost_asdf_asdf_asdf.com
    • Triggering command: /usr/bin/php php vendor/bin/phpunit --color=always --bootstrap ./vendor/wp-cli/wp-cli-tests/tests/bootstrap.php k/wp-cli/wp-cli/php/commands/src/CLI_Cache_Command.php k/wp-cli/wp-cli/php/commands/src/Help_Command.php ndor/bin/git k/wp-cli/wp-cli/git on.php k/wp-cli/wp-cli/-a Extension.php nFin�� ionsIterator.php-v nArrayIterator.php sh iner/Specificatigit nIterator.php ificationLocator-v grep (dns block)
    • Triggering command: /usr/bin/php php vendor/bin/phpunit --color=always --bootstrap ./vendor/wp-cli/wp-cli-tests/tests/bootstrap.php (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>wp-cli subcommands on Windows Powershell</issue_title>
<issue_description>Looking at the official WP documentation here, the wp post delete command should be able to take a subcommand to list all the IDs to delete.

This works well on a Linux like system, but doesn't on Windows Powershell.

I know that it is more a Windows+Powershell issue, but that would be nice if we could find a workaround solution for this.

Expectations

# Delete all pages
$ wp post delete $(wp post list --post_type='page' --format=ids)
Success: Trashed post 1164.
Success: Trashed post 1186.

Reality

# Delete all pages
$ wp post delete $(wp post list --post_type='page' --format=ids)
Success: Trashed post 1164 1186.

In that case, post id 1186 has not been deleted because the subcommand result is interpreted as a single string parameter given to the delete command.
The \WP_CLI\CommandWithDBObject::_delete function that should receive $args as an array of IDs receive in reality one string param with the value 1164 1186 which evaluates as 1164 when casted to integer.

Function parameters type hint
Moreover, the wp_delete_post() function that is called underneath, should receive an integer value as first parameter.

/**
 * @param int  $postid       Optional. Post ID. Default 0.
 * @param bool $force_delete Optional. Whether to bypass Trash and force deletion.
 *                           Default false.
 * @return WP_Post|false|null Post data on success, false or null on failure.
 */
function wp_delete_post( $postid = 0, $force_delete = false ) {}
/**
 * Filters whether a post deletion should take place.
 *
 * @since 4.4.0
 *
 * @param WP_Post|false|null $delete       Whether to go forward with deletion. @TODO description
 * @param WP_Post            $post         Post object.
 * @param bool               $force_delete Whether to bypass the Trash.
 */
$check = apply_filters( 'pre_delete_post', null, $post, $force_delete );

I don't know if parameters hints will be enforced later, but there might be an issue there too as this function and the hooks triggered are all expecting an integer value.</issue_description>

Comments on the Issue (you are @copilot in this section)

@danielbachhuber Thanks for the report, @timotheemoulin !

I moved this issue to wp-cli/wp-cli because I think it's related to how arguments are passed/parsed on Powershell. Here's some past work on PowerShell compatibility, if it's helpful: https://github.com/ericmann/WP-PowerShell

Can you share wp cli info ?

Also, what happens when you run wp post delete 1164 1186? Do you see the same error as wp post delete $(wp post list --post_type='page' --format=ids) ?

As a point of reference, here's where WP-CLI reads $GLOBALS['argv']:

$argv = array_slice( $GLOBALS['argv'], 1 );


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 3 commits February 3, 2026 00:42
…umeric IDs

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix wp-cli subcommands issue on Windows Powershell Fix PowerShell command substitution argument parsing Feb 3, 2026
Copilot AI requested a review from swissspidy February 3, 2026 00:47
@codecov
Copy link

codecov bot commented Feb 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@swissspidy swissspidy requested a review from Copilot February 3, 2026 01:26
@swissspidy
Copy link
Member

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses a command substitution issue on Windows PowerShell where multiple numeric IDs are passed as a single string. The fix is well-targeted, splitting arguments only on Windows and only when they match a strict pattern of space-separated numbers. The changes are accompanied by comprehensive unit and feature tests, ensuring correctness and preventing regressions. I have one minor suggestion to improve performance in the argument splitting logic. Overall, this is a great improvement for Windows users.

// Split on whitespace and add each ID as a separate argument
$ids = preg_split( '/\s+/', $arg, -1, PREG_SPLIT_NO_EMPTY );
if ( false !== $ids ) {
$split_args = array_merge( $split_args, $ids );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better performance, especially when dealing with a large number of IDs, consider using array_push() with the splat operator (...) instead of array_merge(). array_merge() creates a new array in each iteration inside this loop, which can be inefficient. array_push() modifies the array in place and is generally more performant for appending elements.

array_push( $split_args, ...$ids );

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes a Windows PowerShell-specific issue where command substitution (e.g., $(wp post list --format=ids)) returns space-separated IDs as a single string instead of individual arguments, breaking commands that expect multiple ID parameters.

Changes:

  • Added Windows-specific argument splitting logic in Runner.php to detect and split space-separated numeric IDs
  • Added comprehensive unit tests in WindowsArgsTest.php to validate splitting behavior across various scenarios
  • Added Behat integration tests in command.feature to test real-world command execution

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
php/WP_CLI/Runner.php Adds Windows-specific logic in back_compat_conversions() to split arguments matching the pattern of space-separated numeric IDs (2+ numbers), using a conservative regex that only affects the intended use case
tests/WindowsArgsTest.php Adds unit tests covering all edge cases including single IDs, non-numeric strings, mixed arguments, tabs/spaces, and leading/trailing spaces, following established codebase testing patterns
features/command.feature Adds Behat integration tests that simulate Windows behavior using WP_CLI_TEST_IS_WINDOWS environment variable, testing both Windows and non-Windows scenarios

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

wp-cli subcommands on Windows Powershell

2 participants