angular/angular-cli · error · CircularDependencyFoundException
Circular dependency found.
Error message
Circular dependency found.
What it means
PartiallyOrderedSet._checkCircularDependencies walks the dependency graph transitively from a candidate item; if it ever reaches the item itself, the graph has a cycle and it throws CircularDependencyFoundException ('Circular dependency found.'). A topologically ordered structure cannot represent cycles, so insertion is what rejects them. It is invoked from add() and recursively from itself.
Source
Thrown at packages/angular_devkit/core/src/utils/partially-ordered-set.ts:30
constructor() {
super('One of the dependencies is not part of the set.');
}
}
export class CircularDependencyFoundException extends BaseException {
constructor() {
super('Circular dependencies found.');
}
}
/**
* @deprecated Use standard arrays and ensure correct insertion order instead.
*/
export class PartiallyOrderedSet<T> {
private _items = new Map<T, Set<T>>();
protected _checkCircularDependencies(item: T, deps: Set<T>): void {
if (deps.has(item)) {
throw new CircularDependencyFoundException();
}
deps.forEach((dep) => this._checkCircularDependencies(item, this._items.get(dep) || new Set()));
}
clear(): void {
this._items.clear();
}
has(item: T): boolean {
return this._items.has(item);
}
get size(): number {
return this._items.size;
}
forEach(
callbackfn: (value: T, value2: T, set: PartiallyOrderedSet<T>) => void,
thisArg?: any, // eslint-disable-line @typescript-eslint/no-explicit-any
): void {View on GitHub (pinned to bb72145f9a)
Solutions
- Break the cycle: remove the dependency edge that closes the loop from your registration calls
- Register items in dependency order without back-references; model mutual needs with a shared third item
- If the cycle is legitimate, use a plain Map/array instead of PartiallyOrderedSet since it cannot represent cyclic graphs
Example fix
// before
pos.add('a', []);
pos.add('b', ['a']);
pos.add('a', ['b']); // circular
// after
pos.add('a', []);
pos.add('b', ['a']); // keep 'a' dependency-free of 'b' Defensive patterns
Strategy: try-catch
Validate before calling
function hasCycle(items: Map<string, Set<string>>): boolean {
const visiting = new Set<string>(), done = new Set<string>();
const visit = (n: string): boolean => {
if (visiting.has(n)) return true;
if (done.has(n)) return false;
visiting.add(n);
for (const d of items.get(n) || []) if (visit(d)) return true;
visiting.delete(n); done.add(n);
return false;
};
for (const n of items.keys()) if (visit(n)) return true;
return false;
}
// call before mutating registration order Type guard
null
Try / catch
try {
pos.add(item, deps);
} catch (e) {
if ((e as Error).message === 'Circular dependency found.') {
// log item + deps and registration history to locate the closing edge
}
throw e;
} Prevention
- Keep a registration log (item -> deps) so cycles are easy to reconstruct on failure
- Enforce layered registration: lower-level items first, no upward dependencies
- Add a cycle-detection unit test around your registration sequence
- Avoid dynamic re-registration that can close back-references
When it happens
Trigger: add('a', ['b']) where 'b' (directly or transitively via _items dependency sets) already depends on 'a'; e.g. add('a', []), add('b', ['a']), then add('a', ['b']).
Common situations: Registering schema formats or tasks (JobRegistry/schema utilities use partially ordered sets) where mutual registrations create a cycle, dynamically generated registration order in plugin systems, typo making two items depend on each other.
Related errors
AI-assisted analysis of angular/angular-cli@bb72145f9a (2026-08-30).
Data as JSON: /api/errors/9b325db88a1c0f5a.
Report an issue: GitHub.