Skip to content

On Windows path_is_absolute() returns false for absolute paths starting with ABSPATH due to default setting #6115

@dave-slaughter

Description

@dave-slaughter

Bug Report

Describe the current, buggy behavior

When running wp post delete <image-id> --force on Windows I noticed that the alternative image sizes files (image-150x150.jpg etc.) weren't being deleted. I eventually traced this back to path_join() mangling the file paths, which in turn was down to path_is_absolute() returning false for paths that are absolute, which in turn was due to ABSPATH starting with C:/ instead of C:\.

On Windows, when running WordPress through the browser ABSPATH is C:\wp\public/, but in WP-CLI it is C:/wp/public/.

Since path_is_absolute() checks for preg_match( '#^[a-zA-Z]:\\\\#', $path ), then any path starting with ABSPATH returns true when run from the browser (the expected value), but false in WP-CLI.

In WP-CLI this then causes path_join() to mangle the returned paths, which means wp_delete_attachment_files() does not delete alternatives sizes from the uploads directory, and since wp post delete <image-id> --false eventually calls that it fails too.

For info, it seems the only place path_is_absolute() is used is in path_join(), but path_join() is used in a few places.

Describe how other contributors can replicate this bug

You can check the ABSPATH value with wp eval "echo ABSPATH;".

Assuming WordPress is installed in C:\wp\public, when running WordPress through the browser ABSPATH will be C:\wp\public/, but the above will returnC:/wp/public/.

To show how this effects wp post delete:

  • Import a new image that will generate alternative sizes with wp media import image.jpg

  • Check that it has generated alternative sizes with dir wp-content\uploads\2025\09\image* /b, for example

    image-150x150.jpg
    image.jpg
    
  • Delete the image with wp post delete <image-id> --force

  • Check the alternative sizes again dir wp-content\uploads\2025\09\image* /b, in the above example this would be

    image-150x150.jpg
    

Describe what you expect as the correct outcome

After the image is deleted dir wp-content\uploads\2025\09\image* /b should return File Not Found.

wp eval "echo ABSPATH;" should probably return C:\wp\public/ (depending on the solution chosen).

Provide a possible solution

I don't know enough about the code to know whether the best solution is to change path_is_absolute in WordPress core wp-includes\functions.php to handle C:/ and C:\, or the way I fixed it by commenting out the first line in normalize_path() in utils.php as below (though I have no idea what side effects that will have), or probably some other way.

function normalize_path( $path ) {
    // $path = str_replace( '\\', '/', $path );
    ...
}

For info, I first extracted wp-cli.phar to directory wp-cli in my wordpress root, and then I could run it with php wp-cli\vendor\wp-cli\wp-cli\php\boot-fs.php, so then I could change the code.

Workaround

wp post delete <image-id> --force --exec="define('ABSPATH', 'C:\wp\public/');" 2> nul

This sets the value of ABSPATH to the same as when running via a browser, so the delete works OK.

This workaround actually exposed another bug. You will see that I ran it with 2> nul as otherwise it came back with:

         The --path parameter cannot be used when ABSPATH is already defined elsewhere
         ABSPATH is defined as: "C:\wp\public/"

Success: Deleted post 1929.

As you can see, no --path was given. This bug is in WP_CLI\Runner.php:

private static function set_wp_root( $path ) {
    if ( ! defined( 'ABSPATH' ) ) {
        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Declaring a WP native constant.
        define( 'ABSPATH', Utils\normalize_path( Utils\trailingslashit( $path ) ) );
    } elseif ( ! is_null( $path ) ) {
        WP_CLI::error_multi_line(
            [
                'The --path parameter cannot be used when ABSPATH is already defined elsewhere',
                'ABSPATH is defined as: "' . ABSPATH . '"',
            ]
        );
    }

It seems $path is always set as the result of find_wp_root() is passed into this function, so I believe the appropriate fix would be to change the code to:

private function set_wp_root( $path ) {
    if ( ! defined( 'ABSPATH' ) ) {
        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Declaring a WP native constant.
        define( 'ABSPATH', Utils\normalize_path( Utils\trailingslashit( $path ) ) );
    } elseif ( isset( $this->config['path'] ) ) {
        WP_CLI::error_multi_line(
            [
                'The --path parameter cannot be used when ABSPATH is already defined elsewhere',
                'ABSPATH is defined as: "' . ABSPATH . '"',
            ]
        );
    }

and change the call to

// Handle --path parameter
$this::set_wp_root( $this->find_wp_root() );

Let us know what environment you are running this on

OS:     Windows NT 10.0 build 19045 (Windows 10) AMD64
Shell:  C:\WINDOWS\system32\cmd.exe
PHP binary:     C:\Users\[user]\AppData\Roaming\Local\lightning-services\php-8.4.12+0>\bin\win64\php.exe
PHP version:    8.4.12
php.ini used:   C:\Users\[user]\AppData\Roaming\Local\run\duUC4zCgt\conf\php\php.ini
MySQL binary:
MySQL version:
SQL modes:
WP-CLI root dir:        phar://wp-cli.phar/vendor/wp-cli/wp-cli
WP-CLI vendor dir:      phar://wp-cli.phar/vendor
WP_CLI phar path:       phar://C:/Program Files (x86)/Local/resources/extraResources/bin/wp-cli/wp-cli.phar
WP-CLI packages dir:    C:\Users\[user]/.wp-cli/packages/
WP-CLI cache dir:       C:\Users\[user]/.wp-cli/cache
WP-CLI global config:
WP-CLI project config:
WP-CLI version: 2.12.0

Thanks in advance for your help

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions