composer/composer · error · RuntimeException

Could not reliably remove junction for package ' . $package-

Error message

Could not reliably remove junction for package ' . $package->getName()

What it means

Thrown by PathDownloader::remove() on Windows when a package was installed as an NTFS junction and removeJunction() fails. Because blindly recursing into a junction during removal would delete the source tree, Composer fails hard rather than risk data loss when the junction cannot be cleanly removed.

Source

Thrown at src/Composer/Downloader/PathDownloader.php:191

    public function remove(PackageInterface $package, string $path, bool $output = true): PromiseInterface
    {
        $path = Filesystem::trimTrailingSlash($path);
        /**
         * realpath() may resolve Windows junctions to the source path, so we'll check for a junction first
         * to prevent a false positive when checking if the dist and install paths are the same.
         * See https://bugs.php.net/bug.php?id=77639
         *
         * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process
         * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which
         * is disastrous within a junction. So in that case we have no other real choice but to fail hard.
         */
        if (Platform::isWindows() && $this->filesystem->isJunction($path)) {
            if ($output) {
                $this->io->writeError("  - " . UninstallOperation::format($package).", source is still present in $path");
            }
            if (!$this->filesystem->removeJunction($path)) {
                $this->io->writeError("    <warning>Could not remove junction at " . $path . " - is another process locking it?</warning>");
                throw new \RuntimeException('Could not reliably remove junction for package ' . $package->getName());
            }

            return \React\Promise\resolve(null);
        }

        $url = $package->getDistUrl();
        if (null === $url) {
            throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot remove.');
        }

        // ensure that the source path (dist url) is not the same as the install path, which
        // can happen when using custom installers, see https://github.com/composer/composer/pull/9116
        // not using realpath here as we do not want to resolve the symlink to the original dist url
        // it points to
        $fs = new Filesystem;
        $absPath = $fs->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path;
        $absDistUrl = $fs->isAbsolutePath($url) ? $url : Platform::getCwd() . '/' . $url;
        if ($fs->normalizePath($absPath) === $fs->normalizePath($absDistUrl)) {

View on GitHub (pinned to c435d285c9)

Solutions

  1. Close editors, indexers, and other processes that may lock the vendor folder, then re-run.
  2. Manually remove the junction with `rmdir <path>` (Windows rmdir preserves the junction target).
  3. Re-run the composer remove/uninstall operation once the lock is released.
  4. Check Windows permissions / run from a shell with delete rights on the vendor dir.
Defensive patterns

Strategy: retry

Validate before calling

// On Windows, before removing a junction-installed package, check it is removable.
if (PHP_OS_FAMILY === 'Windows' && $filesystem->isJunction($path)) {
    // ensure nothing holds the dir; retry removal a moment later if needed.
}

Try / catch

for ($attempt = 0; $attempt < 3; $attempt++) {
    try {
        $downloader->remove($package, $path);
        break;
    } catch (\RuntimeException $e) {
        if (str_contains($e->getMessage(), 'Could not reliably remove junction') && $attempt < 2) {
            // another process may lock it; release/close editors then retry.
            continue;
        }
        throw $e;
    }
}

Prevention

When it happens

Trigger: Platform::isWindows() is true, isJunction($path) is true, and removeJunction($path) returns false — another process holds a lock on the path, or rmdir could not remove the junction reparse point.

Common situations: An IDE, file indexer, antivirus, or another PHP process has the vendor dir open, preventing junction removal; the junction was already partially removed; Windows permission issue on the vendor folder.

Related errors


AI-assisted analysis of composer/composer@c435d285c9 (2026-08-07). Data as JSON: /api/errors/c9909e9211e7faf7. Report an issue: GitHub.