Skip to content

Fix associative argument quote handling in WP_CLI::runcommand()#6200

Merged
swissspidy merged 8 commits intomainfrom
copilot/fix-associative-arg-quotes
Feb 6, 2026
Merged

Fix associative argument quote handling in WP_CLI::runcommand()#6200
swissspidy merged 8 commits intomainfrom
copilot/fix-associative-arg-quotes

Conversation

Copy link
Contributor

Copilot AI commented Jan 19, 2026

Fix associative argument quote handling in WP_CLI::runcommand()

Issue

When WP_CLI::runcommand() is called with associative arguments containing quoted values (e.g., --bar="baz quax"), the quotes are incorrectly preserved in the parsed value, resulting in "baz quax" instead of baz quax.

Solution

Modified the parse_str_to_argv() function in php/utils.php to:

  1. Use PREG_SET_ORDER to properly access regex capture groups
  2. Detect quoted associative arguments (--key="value" or --key='value') using groups 1 and 2
  3. Detect quoted positional arguments ("value") using groups 5 and 6
  4. Strip outer quotes and unescape inner quotes for both associative and positional arguments consistently
  5. Handle empty quoted values correctly (e.g., --bar="" becomes --bar=)
  6. Simplify logic by leveraging regex capture groups instead of manual quote stripping

Changes Made

  • php/utils.php:

    • Refactored parse_str_to_argv() to use capture groups for quote detection
    • Added consistent unescaping logic for both associative and positional arguments
    • Removed redundant foreach loop for manual quote stripping
    • Used strlen() check to distinguish associative args from positional args
    • Fixed PHPStan static analysis errors
  • tests/UtilsTest.php:

    • Updated test expectations to reflect new behavior
    • Converted testParseStrToArgvStripsQuotesFromAssocValues() to use a data provider
    • Added comprehensive edge case tests for empty quoted values and escaped quotes

Testing

The fix correctly handles:

  • Simple quoted values: --bar="baz quax"baz quax
  • Empty double quotes: --bar=""--bar=
  • Empty single quotes: --bar=''--bar=
  • Escaped double quotes: --bar="baz \"quax\""baz "quax"
  • Escaped single quotes: --bar='baz \'quax\''baz 'quax'
  • Unquoted values: --bar=bazbaz
  • Positional quoted arguments with unescaping: "echo test"echo test

All tests pass successfully:

  • ✅ All 17 testParseStrToArgv tests
  • ✅ 7 comprehensive edge cases in testParseStrToArgvStripsQuotesFromAssocValues
  • ✅ PHPStan static analysis (no errors)
  • ✅ PHPCS code style checks
  • ✅ Manual verification

Benefits of Refactoring

  • More robust: Leverages regex capture groups instead of manual string manipulation
  • More consistent: Applies unescaping logic to both associative and positional arguments
  • Simpler: Removed redundant quote-stripping loop
  • Better type safety: Fixed PHPStan errors by using explicit checks
Original prompt

This section details on the original issue you should resolve

<issue_title>Associative argument contains double quotes if the command is called with WP_CLI::runcommand()</issue_title>
<issue_description>## Bug Report

I created the following command to explain the problem:

/**
 * Some test command.
 */
class SomeCommand
{

    /**
     * A test command.
     *
     * ## OPTIONS
     *
     * <testArg>
     * : Test arg
     *
     * [--test-assoc-arg=<testAssocArg>]
     * : Test assoc arg
     *
     */
    public function test(array $args, array $assocArgs)
    {
        \WP_CLI::line($args[0]);
        \WP_CLI::line($assocArgs['test-assoc-arg']);
    }
}

Now if I run the following command in the console the result is correct:

wp some test "I have spaces" --test-assoc-arg="I have spaces too"

Result:

I have spaces
I have spaces too

The problem occurs if I call the same command with WP_CLI::runcommand():

$result = \WP_CLI::runcommand('some test "I have spaces" --test-assoc-arg="I have spaces too"', [
    'launch' => false, // Launch a new process, or reuse the existing.
    'exit_error' => false, // Exit on error by default.
    'return' => 'all', // Capture and return output, or render in realtime.
    'parse' => false, // Parse returned output as a particular format.
]);

var_dump($result->stdout);

Now I would expect the same output that I got in the console, but instead I get the associative argument in double quotes.

Output:

string(33) "I have spaces
"I have spaces too""

I think this doesn't really make sense, especially because arguments without quotation marks don't have quotation marks in the output, which makes it impossible to correctly remove them manually afterwards.

$result = \WP_CLI::runcommand('some test "I have spaces" --test-assoc-arg=Ihavenospaces', [
    'launch' => false, // Launch a new process, or reuse the existing.
    'exit_error' => false, // Exit on error by default.
    'return' => 'all', // Capture and return output, or render in realtime.
    'parse' => false, // Parse returned output as a particular format.
]);

var_dump($result->stdout);

Output:

string(27) "I have spaces
Ihavenospaces"
```</issue_description>

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

<comments>
<comment_new><author>@danielbachhuber</author><body>
Thanks for the detailed bug report, @korridor. This does seem like something we should get fixed up.</body></comment_new>
<comment_new><author>@thelovekesh</author><body>
I think most of the behavior is intended.

In above output the, the stdout is wrapped in `""` because `var_dump()` is being used to dump the output.

The only behavior in question is: When we parse a command string for args and assoc args(in `WP_CLI::runcommand()`), it includes the assoc args value as it is after `=` e.g. 
```php
$cmd = 'wp cli foo --bar="baz quax"';

// will return the assoc array as
array(1) {
  ["bar"]=>
  string(19) ""baz quax""
}

Now the question is, should we remove single or double quotes while parsing the cmd? </comment_new>
<comment_new>@danielbachhuber
I would expect this behavior:

$cmd = 'wp cli foo --bar="baz quax"';

// will return the assoc array as
array(1) {
  ["bar"]=>
  string(19) "baz quax"
}

It would be great to have test coverage for the various permutations, though (if we don't already).</comment_new>


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
@codecov
Copy link

codecov bot commented Jan 19, 2026

Codecov Report

❌ Patch coverage is 94.11765% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
php/utils.php 94.11% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@swissspidy

This comment was marked as resolved.

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
@swissspidy

This comment was marked as resolved.

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
@swissspidy

This comment was marked as resolved.

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>

This comment was marked as resolved.

Copilot AI changed the title [WIP] Fix associative argument double quotes in WP_CLI command Fix associative argument quote handling in WP_CLI::runcommand() Jan 19, 2026
@swissspidy swissspidy requested a review from Copilot January 19, 2026 16:10

This comment was marked as resolved.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@swissspidy

This comment was marked as resolved.

gemini-code-assist[bot]

This comment was marked as duplicate.

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>

This comment was marked as resolved.

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>

This comment was marked as resolved.

Copilot AI requested a review from swissspidy January 19, 2026 21:40
@swissspidy swissspidy marked this pull request as ready for review January 19, 2026 22:39
@swissspidy swissspidy requested a review from a team as a code owner January 19, 2026 22:39
@swissspidy swissspidy added this to the 3.0.0 milestone Feb 6, 2026
@swissspidy swissspidy merged commit cde8360 into main Feb 6, 2026
53 checks passed
@swissspidy swissspidy deleted the copilot/fix-associative-arg-quotes branch February 6, 2026 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Associative argument contains double quotes if the command is called with WP_CLI::runcommand()

2 participants