From c0804d30c3362fd2475978ca8cfdadd670882b63 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 16:49:23 +0000 Subject: [PATCH 1/7] Initial plan From d562bd14adeea349b387cb7e2e67f94c51b88aa9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 16:55:19 +0000 Subject: [PATCH 2/7] Add --ssh-args global argument implementation Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/flags.feature | 21 +++++++++++++++++++++ php/WP_CLI/Configurator.php | 1 + php/WP_CLI/Runner.php | 21 ++++++++++++++++----- php/class-wp-cli.php | 2 +- php/config-spec.php | 8 ++++++++ 5 files changed, 47 insertions(+), 6 deletions(-) diff --git a/features/flags.feature b/features/flags.feature index 9f89efc8c5..bf92704a69 100644 --- a/features/flags.feature +++ b/features/flags.feature @@ -361,6 +361,27 @@ Feature: Global flags Running SSH command: docker exec --user 'user' 'wordpress' sh -c """ + Scenario: SSH args should be passed to SSH command + When I try `wp --debug --ssh=wordpress --ssh-args="-o ConnectTimeout=5" --version` + Then STDERR should contain: + """ + Running SSH command: ssh '-o ConnectTimeout=5' -T -vvv 'wordpress' 'wp + """ + + Scenario: Multiple SSH args should be passed to SSH command + When I try `wp --debug --ssh=wordpress --ssh-args="-o ConnectTimeout=5" --ssh-args="-o ServerAliveInterval=10" --version` + Then STDERR should contain: + """ + Running SSH command: ssh '-o ConnectTimeout=5' '-o ServerAliveInterval=10' -T -vvv 'wordpress' 'wp + """ + + Scenario: SSH args should be passed to Docker command + When I try `WP_CLI_DOCKER_NO_INTERACTIVE=1 wp --debug --ssh=docker:wordpress --ssh-args="--env MY_VAR=value" --version` + Then STDERR should contain: + """ + Running SSH command: docker exec '--env MY_VAR=value' 'wordpress' sh -c + """ + Scenario: Customize config-spec with WP_CLI_CONFIG_SPEC_FILTER_CALLBACK Given a WP installation And a wp-cli-early-require.php file: diff --git a/php/WP_CLI/Configurator.php b/php/WP_CLI/Configurator.php index 829a5194de..f0c6737e7e 100644 --- a/php/WP_CLI/Configurator.php +++ b/php/WP_CLI/Configurator.php @@ -60,6 +60,7 @@ class Configurator { 'url', 'path', 'ssh', + 'ssh-args', 'http', 'proxyjump', 'key', diff --git a/php/WP_CLI/Runner.php b/php/WP_CLI/Runner.php index 2fbc15a06a..31443ea3a3 100644 --- a/php/WP_CLI/Runner.php +++ b/php/WP_CLI/Runner.php @@ -662,6 +662,12 @@ private function run_ssh_command( $connection_string ): void { private function generate_ssh_command( $bits, $wp_command ) { $escaped_command = ''; + // Get additional SSH arguments if provided. + $ssh_args_config = WP_CLI::get_config( 'ssh-args' ); + $ssh_args = is_array( $ssh_args_config ) && ! empty( $ssh_args_config ) + ? implode( ' ', array_map( 'escapeshellarg', $ssh_args_config ) ) + : ''; + // Set default values. foreach ( [ 'scheme', 'user', 'host', 'port', 'path', 'key', 'proxyjump' ] as $bit ) { if ( ! isset( $bits[ $bit ] ) ) { @@ -689,10 +695,11 @@ private function generate_ssh_command( $bits, $wp_command ) { : 'docker-compose'; if ( 'docker' === $bits['scheme'] ) { - $command = 'docker exec %s%s%s%s%s sh -c %s'; + $command = 'docker exec %s%s%s%s%s%s sh -c %s'; $escaped_command = sprintf( $command, + $ssh_args ? $ssh_args . ' ' : '', $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', $bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '', $is_stdout_tty && ! getenv( 'WP_CLI_DOCKER_NO_TTY' ) ? '-t ' : '', @@ -703,11 +710,12 @@ private function generate_ssh_command( $bits, $wp_command ) { } if ( 'docker-compose' === $bits['scheme'] ) { - $command = '%s exec %s%s%s%s sh -c %s'; + $command = '%s exec %s%s%s%s%s sh -c %s'; $escaped_command = sprintf( $command, $docker_compose_cmd, + $ssh_args ? $ssh_args . ' ' : '', $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', $bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '', $is_stdout_tty || getenv( 'WP_CLI_DOCKER_NO_TTY' ) ? '' : '-T ', @@ -717,11 +725,12 @@ private function generate_ssh_command( $bits, $wp_command ) { } if ( 'docker-compose-run' === $bits['scheme'] ) { - $command = '%s run %s%s%s%s%s %s'; + $command = '%s run %s%s%s%s%s%s %s'; $escaped_command = sprintf( $command, $docker_compose_cmd, + $ssh_args ? $ssh_args . ' ' : '', $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', $bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '', $is_stdout_tty || getenv( 'WP_CLI_DOCKER_NO_TTY' ) ? '' : '-T ', @@ -765,10 +774,11 @@ private function generate_ssh_command( $bits, $wp_command ) { // If we could not resolve the bits still, fallback to just `vagrant ssh` if ( 'vagrant' === $bits['scheme'] ) { - $command = 'vagrant ssh -c %s %s'; + $command = 'vagrant ssh %s-c %s %s'; $escaped_command = sprintf( $command, + $ssh_args ? $ssh_args . ' ' : '', escapeshellarg( $wp_command ), escapeshellarg( $bits['host'] ) ); @@ -777,7 +787,7 @@ private function generate_ssh_command( $bits, $wp_command ) { // Default scheme is SSH. if ( 'ssh' === $bits['scheme'] || null === $bits['scheme'] ) { - $command = 'ssh %s %s %s'; + $command = 'ssh %s%s %s %s'; if ( $bits['user'] ) { $bits['host'] = $bits['user'] . '@' . $bits['host']; @@ -802,6 +812,7 @@ private function generate_ssh_command( $bits, $wp_command ) { $escaped_command = sprintf( $command, + $ssh_args ? $ssh_args . ' ' : '', implode( ' ', array_filter( $command_args ) ), escapeshellarg( $bits['host'] ), escapeshellarg( $wp_command ) diff --git a/php/class-wp-cli.php b/php/class-wp-cli.php index faf7f59153..ca7e7ce7a1 100644 --- a/php/class-wp-cli.php +++ b/php/class-wp-cli.php @@ -23,7 +23,7 @@ /** * Various utilities for WP-CLI commands. * - * @phpstan-type GlobalConfig array{path: string|null, ssh: string|null, http: string|null, url: string|null, user: string|null, 'skip-plugins': true|string[], 'skip-themes': true|string[], 'skip-packages': bool, require: string[], exec: string[], context: string, debug: string|true, prompt: false|string, quiet: bool} + * @phpstan-type GlobalConfig array{path: string|null, ssh: string|null, 'ssh-args': string[], http: string|null, url: string|null, user: string|null, 'skip-plugins': true|string[], 'skip-themes': true|string[], 'skip-packages': bool, require: string[], exec: string[], context: string, debug: string|true, prompt: false|string, quiet: bool} */ class WP_CLI { diff --git a/php/config-spec.php b/php/config-spec.php index 04026d5f33..7443fc3e80 100644 --- a/php/config-spec.php +++ b/php/config-spec.php @@ -19,6 +19,14 @@ 'desc' => 'Perform operation against a remote server over SSH (or a container using scheme of "docker", "docker-compose", "docker-compose-run", "vagrant").', ], + 'ssh-args' => [ + 'runtime' => '=', + 'file' => '', + 'desc' => 'Pass additional arguments to SSH (or other tools specified by --ssh scheme).', + 'multiple' => true, + 'default' => [], + ], + 'http' => [ 'runtime' => '=', 'file' => '', From 4ba47352f5d56a68e3f70356e943d1ffdcf35195 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 16:57:41 +0000 Subject: [PATCH 3/7] Fix vagrant command format to make spacing explicit Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- php/WP_CLI/Runner.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/php/WP_CLI/Runner.php b/php/WP_CLI/Runner.php index 31443ea3a3..ae899d750b 100644 --- a/php/WP_CLI/Runner.php +++ b/php/WP_CLI/Runner.php @@ -774,11 +774,11 @@ private function generate_ssh_command( $bits, $wp_command ) { // If we could not resolve the bits still, fallback to just `vagrant ssh` if ( 'vagrant' === $bits['scheme'] ) { - $command = 'vagrant ssh %s-c %s %s'; + $ssh_args_part = $ssh_args ? $ssh_args . ' ' : ''; + $command = "vagrant ssh {$ssh_args_part}-c %s %s"; $escaped_command = sprintf( $command, - $ssh_args ? $ssh_args . ' ' : '', escapeshellarg( $wp_command ), escapeshellarg( $bits['host'] ) ); From 79ad0796cfcfb4a777bcadb28059110f6ee85904 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 16:59:26 +0000 Subject: [PATCH 4/7] Filter ssh-args from remote command and improve vagrant spacing clarity Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- php/WP_CLI/Runner.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/php/WP_CLI/Runner.php b/php/WP_CLI/Runner.php index ae899d750b..6617d8bf5a 100644 --- a/php/WP_CLI/Runner.php +++ b/php/WP_CLI/Runner.php @@ -631,7 +631,7 @@ private function run_ssh_command( $connection_string ): void { } foreach ( $wp_args as $k => $v ) { - if ( preg_match( '#--ssh=#', $v ) ) { + if ( preg_match( '#--ssh(|-args)=#', $v ) ) { unset( $wp_args[ $k ] ); } } @@ -774,8 +774,11 @@ private function generate_ssh_command( $bits, $wp_command ) { // If we could not resolve the bits still, fallback to just `vagrant ssh` if ( 'vagrant' === $bits['scheme'] ) { - $ssh_args_part = $ssh_args ? $ssh_args . ' ' : ''; - $command = "vagrant ssh {$ssh_args_part}-c %s %s"; + if ( $ssh_args ) { + $command = "vagrant ssh {$ssh_args} -c %s %s"; + } else { + $command = 'vagrant ssh -c %s %s'; + } $escaped_command = sprintf( $command, From a6d20ce521654d12e7693f561b04c927ca7c833e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:01:27 +0000 Subject: [PATCH 5/7] Simplify vagrant command construction (keep is_array check for PHPStan) Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- php/WP_CLI/Runner.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/php/WP_CLI/Runner.php b/php/WP_CLI/Runner.php index 6617d8bf5a..7f745a70e5 100644 --- a/php/WP_CLI/Runner.php +++ b/php/WP_CLI/Runner.php @@ -774,11 +774,7 @@ private function generate_ssh_command( $bits, $wp_command ) { // If we could not resolve the bits still, fallback to just `vagrant ssh` if ( 'vagrant' === $bits['scheme'] ) { - if ( $ssh_args ) { - $command = "vagrant ssh {$ssh_args} -c %s %s"; - } else { - $command = 'vagrant ssh -c %s %s'; - } + $command = 'vagrant ssh' . ( $ssh_args ? ' ' . $ssh_args : '' ) . ' -c %s %s'; $escaped_command = sprintf( $command, From c1ae34fd8087a6e651719b931eab6dac923fdb33 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 25 Jan 2026 12:21:32 -0500 Subject: [PATCH 6/7] Update test --- features/cli.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/cli.feature b/features/cli.feature index 6ce5dd29c1..fa8fb9c2d1 100644 --- a/features/cli.feature +++ b/features/cli.feature @@ -36,7 +36,7 @@ Feature: `wp cli` tasks When I run `wp cli param-dump --with-values | grep -o '"current":' | uniq -c | tr -d ' '` Then STDOUT should be: """ - 19"current": + 20"current": """ And STDERR should be empty And the return code should be 0 From 10edcf569dfccdb81e0612cc2a26de7d966bda9c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:12:35 +0000 Subject: [PATCH 7/7] Fix SSH flag filtering regex to match space-separated flags and prevent false positives Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- php/WP_CLI/Runner.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/WP_CLI/Runner.php b/php/WP_CLI/Runner.php index 677e133e03..192cdb692e 100644 --- a/php/WP_CLI/Runner.php +++ b/php/WP_CLI/Runner.php @@ -656,7 +656,7 @@ private function run_ssh_command( string $connection_string ): void { } foreach ( $wp_args as $k => $v ) { - if ( preg_match( '#--ssh(|-args)=#', (string) $v ) ) { + if ( preg_match( '#^--ssh(?:-args)?(?:=|$)#', (string) $v ) ) { unset( $wp_args[ $k ] ); } }