angular/angular-cli · error

Circular builder alias references detected:

Error message

Circular builder alias references detected: 

What it means

resolveBuilder follows builder alias entries (a builder manifest entry that is itself a 'package:builder' string). It tracks seen builders in a Set and throws if the same builder string is encountered again, preventing infinite recursion from a circular alias chain.

Source

Thrown at packages/angular_devkit/architect/node/node-modules-architect-host.ts:141

  async getBuilderNameForTarget(target: Target): Promise<string> {
    return this.workspaceHost.getBuilderName(target.project, target.target);
  }

  /**
   * Resolve a builder. This needs to be a string which will be used in a dynamic `import()`
   * clause. This should throw if no builder can be found. The dynamic import will throw if
   * it is unsupported.
   * @param builderStr The name of the builder to be used.
   * @returns All the info needed for the builder itself.
   */
  resolveBuilder(
    builderStr: string,
    basePath: string = this._root,
    seenBuilders?: Set<string>,
  ): Promise<NodeModulesBuilderInfo> {
    if (seenBuilders?.has(builderStr)) {
      throw new Error(
        'Circular builder alias references detected: ' + [...seenBuilders, builderStr],
      );
    }

    const [packageName, builderName] = builderStr.split(':', 2);
    if (!builderName) {
      throw new Error('No builder name specified.');
    }

    // Resolve and load the builders manifest from the package's `builders` field, if present
    const packageJsonPath = localRequire.resolve(packageName + '/package.json', {
      paths: [basePath],
    });

    const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as { builders?: string };
    const buildersManifestRawPath = packageJson['builders'];
    if (!buildersManifestRawPath) {
      throw new Error(`Package ${JSON.stringify(packageName)} has no builders defined.`);

View on GitHub (pinned to bb72145f9a)

Solutions

  1. Inspect the package's builders.json and break the alias cycle by pointing entries at real implementation objects.
  2. Check all manifest entries in the chain printed in the error message.
  3. Remove the self-referencing alias and define an 'implementation' and 'schema' path directly.

Example fix

// before (builders.json)
{"builders": {"a": "my-pkg:a", "b": "my-pkg:b"}}
// after
{"builders": {"a": {"implementation": "./a.impl", "schema": "./a.schema.json"}}}
Defensive patterns

Strategy: validation

Validate before calling

function detectAliasCycle(builders) {
  const seen = new Set();
  for (const name of Object.keys(builders)) {
    let cur = builders[name];
    seen.clear();
    while (typeof cur === 'string') {
      if (seen.has(cur)) throw new Error(`Circular builder alias: ${[...seen, cur].join(' -> ')}`);
      seen.add(cur);
      const [pkg, b] = cur.split(':', 2);
      cur = require(`${pkg}/builders.json`)?.builders?.[b];
    }
  }
}

Type guard

const isRealBuilder = (b) => typeof b === 'object' && b !== null && typeof b.implementation === 'string';

Try / catch

try { await host.resolveBuilder(builderStr); } catch (e) { if (String(e.message).includes('Circular builder alias')) { /* fix the builders.json in the offending package */ } else throw e; }

Prevention

When it happens

Trigger: A builders.json manifest entry points (directly or transitively) via string aliases back to itself, e.g. pkg:a -> pkg:b -> pkg:a, and resolveBuilder is called with one of those builders.

Common situations: Hand-written or copied builder manifests with mistaken alias entries; refactoring a package and leaving an alias pointing at its own target.

Related errors


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