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
- Inspect the package's builders.json and break the alias cycle by pointing entries at real implementation objects.
- Check all manifest entries in the chain printed in the error message.
- 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
- Never alias a builder to itself; aliases must terminate at object entries.
- Lint builders.json manifests in CI to catch alias cycles.
- Keep builder manifests minimal and reviewed.
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
- No builder name specified.
- Cannot find builder ${JSON.stringify(builderStr)}.
- The builder requires a target.
- No project name provided and no default project found in wor
- Option "project" is required.
AI-assisted analysis of angular/angular-cli@bb72145f9a (2026-08-30).
Data as JSON: /api/errors/b370e73ecbd673cd.
Report an issue: GitHub.