{"record":{"id":"a5e48080fe5a5092","repo":"angular/angular-cli","slug":"circular-collection-reference-name","errorCode":null,"errorMessage":"Circular collection reference \"${name}\".","messagePattern":"Circular collection reference \"(.+?)\"\\.","errorType":"exception","errorClass":"CircularCollectionException","httpStatus":null,"severity":"error","filePath":"packages/angular_devkit/schematics/src/engine/engine.ts","lineNumber":221,"sourceCode":"\n    collection = new CollectionImpl<CollectionT, SchematicT>(description, this, bases);\n    this._collectionCache.set(name, collection);\n    this._schematicCache.set(collection, new Map());\n\n    return collection;\n  }\n\n  private _createCollectionDescription(\n    name: string,\n    requester?: CollectionDescription<CollectionT>,\n    parentNames?: Set<string>,\n  ): [CollectionDescription<CollectionT>, Array<CollectionDescription<CollectionT>>] {\n    const description = this._host.createCollectionDescription(name, requester);\n    if (!description) {\n      throw new UnknownCollectionException(name);\n    }\n    if (parentNames && parentNames.has(description.name)) {\n      throw new CircularCollectionException(name);\n    }\n\n    const bases = new Array<CollectionDescription<CollectionT>>();\n    if (description.extends) {\n      parentNames = (parentNames || new Set<string>()).add(description.name);\n      for (const baseName of description.extends) {\n        const [base, baseBases] = this._createCollectionDescription(\n          baseName,\n          description,\n          new Set(parentNames),\n        );\n\n        bases.unshift(base, ...baseBases);\n      }\n    }\n\n    return [description, bases];\n  }","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/angular/angular-cli/blob/bb72145f9ab45aee29f523236b3a25cd0813a841/packages/angular_devkit/schematics/src/engine/engine.ts#L203-L239","documentation":"The schematics engine builds a collection's inheritance chain by recursively resolving every collection listed in its \"extends\" field. When a collection ultimately extends itself (directly or through a cycle like A extends B, B extends A), the recursion would never terminate, so _createCollectionDescription tracks visited collection names in a set and throws CircularCollectionException when it revisits one. This is a configuration error in the collection definition files, not a runtime condition.","triggerScenarios":"Calling engine.createCollection(name) (which delegates to _createCollectionDescription) where the collection.json of \"name\" contains an \"extends\" array that includes a collection whose own \"extends\" chain leads back to a collection already in the parentNames set — e.g. collection A extends B and B extends A, or A extends A.","commonSituations":"Hand-editing or scaffolding a custom collection.json and adding a wrong entry to \"extends\"; refactoring/copying collections and accidentally pointing two collections at each other; publishing a tooling collection with an extends typo that only surfaces when a user runs a schematic from it.","solutions":["Open the collection.json for the named collection and inspect its \"extends\" array for a self-reference or mutual reference with another collection.","Break the cycle: remove the offending entry from \"extends\" (the base collection should not inherit from the derived one).","If inheritance was meant to reuse schematics, inline or reorganize the shared schematics instead of creating a circular extends chain.","Pin/upgrade to fixed versions of third-party collections whose extends chains were erroneous."],"exampleFix":"// before — collection.json of \"my-utils\"\n{\n  \"name\": \"my-utils\",\n  \"extends\": [\"my-base\"]\n}\n// and collection.json of \"my-base\"\n{\n  \"name\": \"my-base\",\n  \"extends\": [\"my-utils\"]\n}\n\n// after — remove the cycle; only the derived collection extends\n// my-utils/collection.json\n{\n  \"name\": \"my-utils\",\n  \"extends\": [\"my-base\"]\n}\n// my-base/collection.json\n{\n  \"name\": \"my-base\"\n}","handlingStrategy":"validation","validationCode":"import * as fs from 'fs';\nfunction hasCircularExtends(collectionJsonPaths: string[]): boolean {\n  const graph = new Map<string, string[]>();\n  for (const p of collectionJsonPaths) {\n    const json = JSON.parse(fs.readFileSync(p, 'utf8'));\n    graph.set(json.name, json.extends ?? []);\n  }\n  const visiting = new Set<string>(), visited = new Set<string>();\n  const dfs = (n: string): boolean => {\n    if (visiting.has(n)) return true;\n    if (visited.has(n)) return false;\n    visiting.add(n);\n    for (const b of graph.get(n) ?? []) if (dfs(b)) return true;\n    visiting.delete(n); visited.add(n);\n    return false;\n  };\n  return [...graph.keys()].some(dfs);\n}","typeGuard":null,"tryCatchPattern":"import { CircularCollectionException } from '@angular-devkit/schematics';\ntry {\n  const collection = engine.createCollection('my-collection');\n} catch (e) {\n  if (e instanceof CircularCollectionException) {\n    // fix collection.json \"extends\" chain\n  } else { throw e; }\n}","preventionTips":["Keep \"extends\" chains one-directional: a base collection must never extend a derived one.","Add a unit test that calls createCollection on every collection you ship to catch cycles at CI time.","Lint collection.json files with a small script validating that extends references resolve and are acyclic."],"tags":["schematics","configuration","recursion","collection"],"backgroundTag":"circular-dependency","analyzedSha":"bb72145f9ab45aee29f523236b3a25cd0813a841","analyzedAt":"2026-08-30T02:47:34.745Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}