{"record":{"id":"c9909e9211e7faf7","repo":"composer/composer","slug":"could-not-reliably-remove-junction-for-package","errorCode":null,"errorMessage":"Could not reliably remove junction for package ' . $package->getName()","messagePattern":"Could not reliably remove junction for package ' \\. \\$package->getName\\(\\)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/Composer/Downloader/PathDownloader.php","lineNumber":191,"sourceCode":"    public function remove(PackageInterface $package, string $path, bool $output = true): PromiseInterface\n    {\n        $path = Filesystem::trimTrailingSlash($path);\n        /**\n         * realpath() may resolve Windows junctions to the source path, so we'll check for a junction first\n         * to prevent a false positive when checking if the dist and install paths are the same.\n         * See https://bugs.php.net/bug.php?id=77639\n         *\n         * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process\n         * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which\n         * is disastrous within a junction. So in that case we have no other real choice but to fail hard.\n         */\n        if (Platform::isWindows() && $this->filesystem->isJunction($path)) {\n            if ($output) {\n                $this->io->writeError(\"  - \" . UninstallOperation::format($package).\", source is still present in $path\");\n            }\n            if (!$this->filesystem->removeJunction($path)) {\n                $this->io->writeError(\"    <warning>Could not remove junction at \" . $path . \" - is another process locking it?</warning>\");\n                throw new \\RuntimeException('Could not reliably remove junction for package ' . $package->getName());\n            }\n\n            return \\React\\Promise\\resolve(null);\n        }\n\n        $url = $package->getDistUrl();\n        if (null === $url) {\n            throw new \\RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot remove.');\n        }\n\n        // ensure that the source path (dist url) is not the same as the install path, which\n        // can happen when using custom installers, see https://github.com/composer/composer/pull/9116\n        // not using realpath here as we do not want to resolve the symlink to the original dist url\n        // it points to\n        $fs = new Filesystem;\n        $absPath = $fs->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path;\n        $absDistUrl = $fs->isAbsolutePath($url) ? $url : Platform::getCwd() . '/' . $url;\n        if ($fs->normalizePath($absPath) === $fs->normalizePath($absDistUrl)) {","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/composer/composer/blob/c435d285c9120efdca35696769c72ea9fdcc0466/src/Composer/Downloader/PathDownloader.php#L173-L209","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// On Windows, before removing a junction-installed package, check it is removable.\nif (PHP_OS_FAMILY === 'Windows' && $filesystem->isJunction($path)) {\n    // ensure nothing holds the dir; retry removal a moment later if needed.\n}","typeGuard":null,"tryCatchPattern":"for ($attempt = 0; $attempt < 3; $attempt++) {\n    try {\n        $downloader->remove($package, $path);\n        break;\n    } catch (\\RuntimeException $e) {\n        if (str_contains($e->getMessage(), 'Could not reliably remove junction') && $attempt < 2) {\n            // another process may lock it; release/close editors then retry.\n            continue;\n        }\n        throw $e;\n    }\n}","preventionTips":["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."],"tags":["windows","junction","filesystem","file-lock","removal"],"backgroundTag":null,"analyzedSha":"c435d285c9120efdca35696769c72ea9fdcc0466","analyzedAt":"2026-08-07T18:58:23.525Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}