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
- Close editors, indexers, and other processes that may lock the vendor folder, then re-run.
- Manually remove the junction with `rmdir <path>` (Windows rmdir preserves the junction target).
- Re-run the composer remove/uninstall operation once the lock is released.
- 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
- Close IDEs/indexers/antivirus before removing junction-installed packages on Windows.
- Manually `rmdir <path>` if Composer cannot remove the junction.
- Keep vendor on a local NTFS volume to reduce lock contention.
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
- {action} failed: "{localFilename}" could not be written. {er
- Could not completely delete %s, aborting.
- Symlink from "%s" to "%s" failed!
- You are on an old Windows / old PHP combo which does not all
- Could not completely delete {path}, aborting.
AI-assisted analysis of composer/composer@c435d285c9 (2026-08-07).
Data as JSON: /api/errors/c9909e9211e7faf7.
Report an issue: GitHub.