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

  1. Break the cycle: remove the dependency edge that closes the loop from your registration calls
  2. Register items in dependency order without back-references; model mutual needs with a shared third item
  3. 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

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.