Well this was a very interesting problem to run into. We’d migrated a website to a new host and WP-CLI was throwing errors complaining about an issue with the FTP Connection. What, for real? What would WP-CLI want with an FTP Connection? It wasn’t all commands though; wp core version ran without issue but others like wp plugin list kept complaining. What’s more, running WP-CLI on another website on the exact same host proceeded without issue. What was going on?

The Errors

Below is a truncated and cleaned up version of the errors:

PHP Fatal error:  Uncaught TypeError: ftp_fget(): Argument #1 ($ftp) must be of type FTP\Connection, null given in /PATH_TO_WEBSITE_ROOT/wp-admin/includes/class-wp-filesystem-ftpext.php:146
Stack trace:

#0 /PATH_TO_WEBSITE_ROOT/wp-admin/includes/class-wp-filesystem-ftpext.php(146): ftp_fget()

#1 /PATH_TO_WEBSITE_ROOT/wp-content/themes/THE_THEME/THE_FILE.php(LINE X): THE_CLASS->THE_FUNCTION()

... (More error traces)

My first thought was there was an issue with the WordPress theme because it⁠⁠⁠⁠ appears right there in the stack trace. Since this particular website used a third-party theme we did the obvious thing and updated it hoping that would get rid of the problem. No such luck.

Upon deeper inspection we discovered the theme was using $wp_filesystem->get_contents() to read several of its own files to display as a list in the WordPress Dashboard*. WordPress has several different methods of reading files and you can retrieve the method used via the get_filesystem_method() function. Digging into this function shows that the method WordPress uses is based on file ownership: it creates a temporary file and checks the owner of the temporary file against the owner of the currently executing one. If the owners match then WordPress reads files via the direct method otherwise it will use another method which in our case was ftpext (the stack trace shows it uses the file class-wp-filesystem-ftpext.php).

// Attempt to determine the file owner of the WordPress files, and that of newly created files.
$wp_file_owner = false;
$temp_file_owner = false;
if ( function_exists( 'fileowner' ) ) {
  $wp_file_owner = @fileowner( __FILE__ );
  $temp_file_owner = @fileowner( $temp_file_name );
}

if ( false !== $wp_file_owner && $wp_file_owner === $temp_file_owner ) {
...

And this was the cause of our issue; we were running WP-CLI on the command-line as a different user which forced WordPress to use ftpext.

File ownership

Remember how I said this website had been recently migrated to a new host? For added security we set up the host to run each application/website as its own non-login user who was also the owner of that application⁠⁠⁠⁠’s files. However, when we ran WP-CLI we were doing so as a different logged-in user.

The solution

It⁠⁠⁠⁠’s actually very simple; run WP-CLI as the owner of the application/website files:

sudo -u OTHER_USER wp plugin list 

Another method that works is to force WordPress to use the direct method of reading files. This can be done by adding  define('FS_METHOD', 'direct'); to wp-config.php but I’m not sure about the security ramifications of doing this.

But… why is this happening at all?

🤷‍♀️ I don⁠⁠⁠⁠’t know why WP-CLI was effectively trying to execute the theme code even when it’s just trying to retrieve a list of installed plugins. It makes me wonder if there is a SHORTINIT version for WP-CLI.


* The other website where WP-CLI ran without issue had a theme which did not $wp_filesystem->get_contents() so that⁠⁠⁠⁠’s probably why there were no errors.