composer/composer · error · RuntimeException

Symlink from "%s" to "%s" failed!

Error message

Symlink from "%s" to "%s" failed!

What it means

Thrown by PathDownloader::install() (line 138-149) when the symlink/junction creation throws a Symfony IOException and the mirror strategy is NOT in the allowed strategies. This happens specifically when the package forces symlink-only mode via transport option symlink:true, so the code cannot fall back to mirroring.

Source

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

                        if (!$this->filesystem->isAbsolutePath($absolutePath)) {
                            $absolutePath = Platform::getCwd() . DIRECTORY_SEPARATOR . $path;
                        }
                        $shortestPath = $this->filesystem->findShortestPath($absolutePath, $realUrl, false, true);
                        $symfonyFilesystem->symlink($shortestPath.'/', $path);
                    } else {
                        $symfonyFilesystem->symlink($realUrl.'/', $path);
                    }
                }
            } catch (IOException $e) {
                if (in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) {
                    if ($output) {
                        $this->io->writeError('');
                        $this->io->writeError('    <error>Symlink failed, fallback to use mirroring!</error>');
                    }
                    $currentStrategy = self::STRATEGY_MIRROR;
                    $isFallback = true;
                } else {
                    throw new \RuntimeException(sprintf('Symlink from "%s" to "%s" failed!', $realUrl, $path));
                }
            }
        }

        // Fallback if symlink failed or if symlink is not allowed for the package
        if (self::STRATEGY_MIRROR === $currentStrategy) {
            $realUrl = $this->filesystem->normalizePath($realUrl);

            if ($output) {
                $this->io->writeError(sprintf('%sMirroring from %s', $isFallback ? '    ' : '', $url), false);
            }
            $iterator = new ArchivableFilesFinder($realUrl, []);
            $symfonyFilesystem->mirror($realUrl, $path, $iterator);
        }

        if ($output) {
            $this->io->writeError('');
        }

View on GitHub (pinned to 6ffc117740)

Solutions

  1. Remove the symlink:true transport option so Composer can fall back to mirroring
  2. Grant permissions to create symlinks (on Linux ensure the user can symlink; on Windows enable Developer Mode or run as admin)
  3. Ensure the install path does not already exist before install
  4. On Windows, shorten the path to avoid MAX_PATH limits

Example fix

// before: forces symlink, no fallback
"repositories": [{"type":"path","url":"./pkg","options":{"symlink":true}}]
// after: allow mirror fallback
"repositories": [{"type":"path","url":"./pkg","options":{"symlink":null}}]
Defensive patterns

Strategy: fallback

Validate before calling

// Allow mirror fallback instead of forcing symlink-only
$options = $package->getTransportOptions();
if (($options['symlink'] ?? null) === true && !function_exists('symlink')) {
    $options['symlink'] = false; // permit mirroring
    $package->setTransportOptions($options);
}

Try / catch

try {
    $downloader->install($package, $path);
} catch (\RuntimeException $e) {
    if (str_contains($e->getMessage(), 'Symlink from') && str_contains($e->getMessage(), 'failed')) {
        // relax symlink:true and retry with mirroring
        $package->setTransportOptions(['symlink' => false] + $package->getTransportOptions());
        $downloader->install($package, $path);
    } else { throw $e; }
}

Prevention

When it happens

Trigger: install() selects STRATEGY_SYMLINK, SymfonyFilesystem::symlink() or filesystem->junction() throws IOException, and in_array(STRATEGY_MIRROR, allowedStrategies) is false because symlink:true was set.

Common situations: symlink:true forced in the path repo options combined with: insufficient permissions to create symlinks, the install path already exists, Windows path-too-long limits, or antivirus blocking junction creation.

Related errors


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