angular/angular-cli · error

Circular schematic reference detected

Error message

Circular schematic reference detected: ${JSON.stringify(Array.from(references))}

What it means

NodeModuleEngineHost.resolve tracks the set of packages involved while resolving a schematic collection through node module resolution. If a package reappears as the requester (i.e. package A requires B which requires A, directly or transitively), resolve throws this Error with the full cycle of package names, preventing infinite recursion.

Solutions

  1. Inspect the package list in the error to identify the cycle (e.g. ["@a/pkg", "@b/pkg"]).
  2. Break the cycle: make one package's collection not `extends` or reference the other (inline the shared schematics or extract a third base package).
  3. Check the offending package.json/collection.json `extends`/dependency fields and remove the self- or circular reference.
  4. Rebuild and reinstall the affected local packages, then retry.

Example fix

// before (pkg-b collection.json)
{ "extends": ["@a/pkg"], "schematics": { ... } } // while @a/pkg collection.json extends @b/pkg
// after (break the cycle)
{ "schematics": { ... } } // remove the extends entry from one of the two packages
Defensive patterns

Strategy: try-catch

Validate before calling

// Detect extends cycles before loading (graph check over collection.json extends fields)
function detectCycle(graph: Record<string, string[]>, start: string) {
  const seen = new Set<string>();
  const dfs = (n: string): boolean => {
    if (n === start && seen.size) return true;
    if (seen.has(n)) return true;
    seen.add(n);
    return (graph[n] || []).some(dfs);
  };
  if (dfs(start)) throw new Error(`Circular collection extends involving ${start}`);
}

Try / catch

try {
  const collection = engine.createCollection(name);
} catch (e) {
  if (String(e.message).includes('Circular schematic reference detected')) {
    console.error(e.message); // lists the cycle; break it in the listed package's collection.json
    process.exitCode = 1;
  } else throw e;
}

Prevention

When it happens

Trigger: Resolving a collection whose package.json (or an intermediate dependency's) points back to a package already in the `references` set — e.g. package A's schematic extends/references collection of B, and B references A; `references.has(requester)` becomes true.

Common situations: Two schematic packages that `extends` each other's collections; a package declaring itself as its own dependency or extending its own collection; accidentally publishing a collection.json whose `extends` points at the same package name; circular npm dependencies introduced by a refactor.

Related errors


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

Appendix: source

Thrown at packages/angular_devkit/schematics/tools/node-module-engine-host.ts:41

  constructor(name: string) {
    super(`Package ${JSON.stringify(name)} was found but does not support schematics.`);
  }
}

/**
 * A simple EngineHost that uses NodeModules to resolve collections.
 */
export class NodeModulesEngineHost extends FileSystemEngineHostBase {
  constructor(private readonly paths?: string[]) {
    super();
  }

  private resolve(name: string, requester?: string, references = new Set<string>()): string {
    // Keep track of the package requesting the schematic, in order to avoid infinite recursion
    if (requester) {
      if (references.has(requester)) {
        references.add(requester);
        throw new Error(
          'Circular schematic reference detected: ' + JSON.stringify(Array.from(references)),
        );
      } else {
        references.add(requester);
      }
    }

    let collectionPath: string | undefined = undefined;
    const resolveOptions = {
      paths: requester ? [dirname(requester), ...(this.paths || [])] : this.paths,
    };

    // Try to resolve as a package
    try {
      const packageJsonPath = require.resolve(`${name}/package.json`, resolveOptions);
      const { schematics } = require(packageJsonPath);

      if (!schematics || typeof schematics !== 'string') {

View on GitHub (pinned to bb72145f9a)