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

  1. Read the 'A -> B -> ... -> A' chain in the message to identify the cycle's nodes.
  2. Break the cycle by removing one direction of the import or deferring it with a dynamic import().
  3. Extract the shared logic into a third module both sides import instead of each other.
  4. 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

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


AI-assisted analysis of symfony/symfony@698e28026c (2026-08-06). Data as JSON: /api/errors/2125acaa0a86498e. Report an issue: GitHub.