symfony/symfony · error · CircularAssetsException
Circular reference detected while creating asset for "%s": "
Error message
Circular reference detected while creating asset for "%s": "%s".
What it means
MappedAssetFactory recursively compiles dependencies while building a MappedAsset. It tracks logical paths currently being created in assetsBeingCreated; if createMappedAsset() is re-entered for a logicalPath already in that set, it throws CircularAssetsException, printing the dependency chain. The exception exposes getIncompleteMappedAsset() so callers can break the cycle and continue.
Source
Thrown at src/Symfony/Component/AssetMapper/Factory/MappedAssetFactory.php:42
class MappedAssetFactory implements MappedAssetFactoryInterface
{
private const PREDIGESTED_REGEX = '/-([0-9a-zA-Z]{7,128}\.digested)/';
private const PUBLIC_DIGEST_LENGTH = 7;
private array $assetsCache = [];
private array $assetsBeingCreated = [];
public function __construct(
private readonly PublicAssetsPathResolverInterface $assetsPathResolver,
private readonly AssetMapperCompiler $compiler,
private readonly string $vendorDir,
) {
}
public function createMappedAsset(string $logicalPath, string $sourcePath): ?MappedAsset
{
if (isset($this->assetsBeingCreated[$logicalPath])) {
throw new CircularAssetsException($this->assetsCache[$logicalPath], \sprintf('Circular reference detected while creating asset for "%s": "%s".', $logicalPath, implode(' -> ', $this->assetsBeingCreated).' -> '.$logicalPath));
}
$this->assetsBeingCreated[$logicalPath] = $logicalPath;
if (!isset($this->assetsCache[$logicalPath])) {
$isVendor = $this->isVendor($sourcePath);
$asset = new MappedAsset($logicalPath, $sourcePath, $this->assetsPathResolver->resolvePublicPath($logicalPath), isVendor: $isVendor);
$this->assetsCache[$logicalPath] = $asset;
$content = $this->compileContent($asset);
[$digest, $isPredigested] = $this->getDigest($asset, $content);
$asset = new MappedAsset(
$asset->logicalPath,
$asset->sourcePath,
$asset->publicPathWithoutDigest,
$this->getPublicPath($asset, $content),
$content,
$digest,View on GitHub (pinned to 698e28026c)
Solutions
- Read the 'A -> B -> ... -> A' chain in the message to identify the cycle's nodes.
- Break the cycle by removing one direction of the import or deferring it with a dynamic import().
- Extract the shared logic into a third module both sides import instead of each other.
- If the cycle only appears in comments/non-executable code, make sure it is not parsed as a real import.
Example fix
// before: a.js <-> b.js import each other at top level
// a.js
import { b } from './b.js';
// b.js
import { a } from './a.js';
// after: shared module removes the cycle
// shared.js exports the common code; a.js and b.js both import './shared.js' Defensive patterns
Strategy: try-catch
Validate before calling
// Run a cycle detector over the import graph before building. // e.g. `npx madge --circular assets/` in CI to catch top-level circular imports early.
Try / catch
// Break the cycle gracefully using the incomplete asset carried by the exception.
try {
$asset = $factory->createMappedAsset($logicalPath, $sourcePath);
} catch (\Symfony\Component\AssetMapper\Exception\CircularAssetsException $e) {
$asset = $e->getIncompleteMappedAsset();
error_log('Circular asset reference, using incomplete asset: '.$e->getMessage());
} Prevention
- Avoid top-level circular imports; refactor shared code into a third module.
- Defer one side of a cycle with dynamic import().
- Run a circular-dependency detector (madge, dependency-cruiser) in CI.
When it happens
Trigger: Asset A's compilation pulls in asset B (via a compiler dependency) which transitively requires A again before A's entry is finalized; createMappedAsset() detects logicalPath in assetsBeingCreated and throws at line 42.
Common situations: Genuine top-level circular ES module imports (a code smell even in browsers); a self-referencing import inside a comment that the JS parser treats as executable; custom compilers that add cross dependencies.
Related errors
- Cannot find imported JavaScript asset "%s" in asset mapper.
- Failed to compile JavaScript import paths in "%s". Error: "%
- Unable to find asset "%s" imported from "%s".
- Cannot find JavaScript asset "%s" (imported in "%s") in asse
- AssetMapper support cannot be enabled as the AssetMapper com
AI-assisted analysis of symfony/symfony@698e28026c (2026-08-06).
Data as JSON: /api/errors/2125acaa0a86498e.
Report an issue: GitHub.