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
- Remove the symlink:true transport option so Composer can fall back to mirroring
- Grant permissions to create symlinks (on Linux ensure the user can symlink; on Windows enable Developer Mode or run as admin)
- Ensure the install path does not already exist before install
- 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
- Avoid forcing symlink:true unless you control the environment's symlink permissions
- On Windows enable Developer Mode or run elevated for symlink/junction rights
- Ensure the install path is cleared before install so symlink targets are clean
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
- You are on an old Windows / old PHP combo which does not all
- {action} failed: "{localFilename}" could not be written. {er
- Cannot junction to "%s" as it is not a directory.
- Failed to create junction to "%s" at "%s".
- Could not reliably remove junction for package {name}
AI-assisted analysis of composer/composer@6ffc117740 (2026-08-07).
Data as JSON: /api/errors/b15e31a5923d4ac5.
Report an issue: GitHub.