angular/angular-cli · error · CircularCollectionException

Circular collection reference "${name}".

Error message

Circular collection reference "${name}".

What it means

The schematics engine builds a collection's inheritance chain by recursively resolving every collection listed in its "extends" field. When a collection ultimately extends itself (directly or through a cycle like A extends B, B extends A), the recursion would never terminate, so _createCollectionDescription tracks visited collection names in a set and throws CircularCollectionException when it revisits one. This is a configuration error in the collection definition files, not a runtime condition.

Source

Thrown at packages/angular_devkit/schematics/src/engine/engine.ts:221

    collection = new CollectionImpl<CollectionT, SchematicT>(description, this, bases);
    this._collectionCache.set(name, collection);
    this._schematicCache.set(collection, new Map());

    return collection;
  }

  private _createCollectionDescription(
    name: string,
    requester?: CollectionDescription<CollectionT>,
    parentNames?: Set<string>,
  ): [CollectionDescription<CollectionT>, Array<CollectionDescription<CollectionT>>] {
    const description = this._host.createCollectionDescription(name, requester);
    if (!description) {
      throw new UnknownCollectionException(name);
    }
    if (parentNames && parentNames.has(description.name)) {
      throw new CircularCollectionException(name);
    }

    const bases = new Array<CollectionDescription<CollectionT>>();
    if (description.extends) {
      parentNames = (parentNames || new Set<string>()).add(description.name);
      for (const baseName of description.extends) {
        const [base, baseBases] = this._createCollectionDescription(
          baseName,
          description,
          new Set(parentNames),
        );

        bases.unshift(base, ...baseBases);
      }
    }

    return [description, bases];
  }

View on GitHub (pinned to bb72145f9a)

Solutions

  1. Open the collection.json for the named collection and inspect its "extends" array for a self-reference or mutual reference with another collection.
  2. Break the cycle: remove the offending entry from "extends" (the base collection should not inherit from the derived one).
  3. If inheritance was meant to reuse schematics, inline or reorganize the shared schematics instead of creating a circular extends chain.
  4. Pin/upgrade to fixed versions of third-party collections whose extends chains were erroneous.

Example fix

// before — collection.json of "my-utils"
{
  "name": "my-utils",
  "extends": ["my-base"]
}
// and collection.json of "my-base"
{
  "name": "my-base",
  "extends": ["my-utils"]
}

// after — remove the cycle; only the derived collection extends
// my-utils/collection.json
{
  "name": "my-utils",
  "extends": ["my-base"]
}
// my-base/collection.json
{
  "name": "my-base"
}
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from 'fs';
function hasCircularExtends(collectionJsonPaths: string[]): boolean {
  const graph = new Map<string, string[]>();
  for (const p of collectionJsonPaths) {
    const json = JSON.parse(fs.readFileSync(p, 'utf8'));
    graph.set(json.name, json.extends ?? []);
  }
  const visiting = new Set<string>(), visited = new Set<string>();
  const dfs = (n: string): boolean => {
    if (visiting.has(n)) return true;
    if (visited.has(n)) return false;
    visiting.add(n);
    for (const b of graph.get(n) ?? []) if (dfs(b)) return true;
    visiting.delete(n); visited.add(n);
    return false;
  };
  return [...graph.keys()].some(dfs);
}

Try / catch

import { CircularCollectionException } from '@angular-devkit/schematics';
try {
  const collection = engine.createCollection('my-collection');
} catch (e) {
  if (e instanceof CircularCollectionException) {
    // fix collection.json "extends" chain
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling engine.createCollection(name) (which delegates to _createCollectionDescription) where the collection.json of "name" contains an "extends" array that includes a collection whose own "extends" chain leads back to a collection already in the parentNames set — e.g. collection A extends B and B extends A, or A extends A.

Common situations: Hand-editing or scaffolding a custom collection.json and adding a wrong entry to "extends"; refactoring/copying collections and accidentally pointing two collections at each other; publishing a tooling collection with an extends typo that only surfaces when a user runs a schematic from it.

Related errors


AI-assisted analysis of angular/angular-cli@bb72145f9a (2026-08-30). Data as JSON: /api/errors/a5e48080fe5a5092. Report an issue: GitHub.