{"record":{"id":"91c29c7757c906a7","repo":"nodejs/node","slug":"cycles-in-dependency-graph-detected-cycles","errorCode":null,"errorMessage":"Cycles in dependency graph detected:\n{cycles}","messagePattern":"Cycles in dependency graph detected:\n(.+?)","errorType":"exception","errorClass":"DependencyGraphNode.CircularException","httpStatus":null,"severity":"error","filePath":"tools/gyp/pylib/gyp/input.py","lineNumber":1974,"sourceCode":"\n    flat_list = root_node.FlattenToList()\n\n    # If there's anything left unvisited, there must be a circular dependency\n    # (cycle).\n    if len(flat_list) != len(targets):\n        if not root_node.dependents:\n            # If all targets have dependencies, add the first target as a dependent\n            # of root_node so that the cycle can be discovered from root_node.\n            target = next(iter(targets))\n            target_node = dependency_nodes[target]\n            target_node.dependencies.append(root_node)\n            root_node.dependents.append(target_node)\n\n        cycles = []\n        for cycle in root_node.FindCycles():\n            paths = [node.ref for node in cycle]\n            cycles.append(\"Cycle: %s\" % \" -> \".join(paths))\n        raise DependencyGraphNode.CircularException(\n            \"Cycles in dependency graph detected:\\n\" + \"\\n\".join(cycles)\n        )\n\n    return [dependency_nodes, flat_list]\n\n\ndef VerifyNoGYPFileCircularDependencies(targets):\n    # Create a DependencyGraphNode for each gyp file containing a target.  Put\n    # it into a dict for easy access.\n    dependency_nodes = {}\n    for target in targets:\n        build_file = gyp.common.BuildFile(target)\n        if build_file not in dependency_nodes:\n            dependency_nodes[build_file] = DependencyGraphNode(build_file)\n\n    # Set up the dependency links.\n    for target, spec in targets.items():\n        build_file = gyp.common.BuildFile(target)","sourceCodeStart":1956,"sourceCodeEnd":1992,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gyp/pylib/gyp/input.py#L1956-L1992","documentation":"After building target nodes, gyp flattens the graph from a synthetic root_node. If the flattened list length != number of targets, some nodes are mutually reachable only through a cycle. gyp then runs FindCycles and raises DependencyGraphNode.CircularException listing each cycle path as TargetA -> TargetB -> ... -> TargetA.","triggerScenarios":"A set of targets form a dependency cycle (A depends on B, B depends on A, or longer rings). The length-mismatch check plus FindCycles enumerates and reports them. If no node is a root dependent, the first target is artificially attached to root_node so the cycle becomes discoverable.","commonSituations":"Two libraries mutually depending; a target accidentally listing a dep that transitively points back; refactoring that moved a dependency from a child to a parent creating a ring; conditional dependencies that only cycle in certain configs.","solutions":["Identify the cycle in the reported path and remove or redirect one edge to break the ring.","Move shared code into a third target that both cyclic members depend on instead of each other.","Use direct_dependent_settings/all_dependent_settings to share settings without adding a hard dependency edge.","Re-run after each edge change to confirm FlattenToList succeeds."],"exampleFix":"// before: a.gyp:libA depends on libB, b.gyp:libB depends on libA\n// break the ring by extracting common code\n'dependencies': ['common.gyp:common']","handlingStrategy":"validation","validationCode":"def has_cycle(targets):\n    WHITE, GRAY, BLACK = 0, 1, 2\n    color = {t: WHITE for t in targets}\n    def dfs(n):\n        color[n] = GRAY\n        for d in targets[n].get('dependencies', []):\n            if d not in color: continue\n            if color[d] == GRAY: return True\n            if color[d] == WHITE and dfs(d): return True\n        color[n] = BLACK\n        return False\n    return any(color[t] == WHITE and dfs(t) for t in targets)\n","typeGuard":null,"tryCatchPattern":"try:\n    gyp.main(args)\nexcept gyp.input.DependencyGraphNode.CircularException as e:\n    print('Dependency cycle:', e); raise","preventionTips":["Model dependencies as a DAG; never add a back-edge.","Use settings propagation instead of hard deps when only flags are shared.","Run cycle detection in CI on .gyp changes."],"tags":["gyp","dependency-graph","cycle","circular-dependency","build-config"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}