{"record":{"id":"e8cd9bbf90e7182d","repo":"hashicorp/packer","slug":"cycle-s","errorCode":null,"errorMessage":"Cycle: %s","messagePattern":"Cycle: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/dag/dag.go","lineNumber":42,"sourceCode":"type DepthWalkFunc func(Vertex, int) error\n\nfunc (g *AcyclicGraph) DirectedGraph() Grapher {\n\treturn g\n}\n\n// Validate validates the DAG. A DAG is valid if it has no cycles or self-referencing vertex.\nfunc (g *AcyclicGraph) Validate() error {\n\t// Look for cycles of more than 1 component\n\tvar err error\n\tcycles := g.Cycles()\n\tif len(cycles) > 0 {\n\t\tfor _, cycle := range cycles {\n\t\t\tcycleStr := make([]string, len(cycle))\n\t\t\tfor j, vertex := range cycle {\n\t\t\t\tcycleStr[j] = VertexName(vertex)\n\t\t\t}\n\n\t\t\terr = errors.Join(err, fmt.Errorf(\n\t\t\t\t\"Cycle: %s\", strings.Join(cycleStr, \", \")))\n\t\t}\n\t}\n\n\t// Look for cycles to self\n\tfor _, e := range g.Edges() {\n\t\tif e.Source() == e.Target() {\n\t\t\terr = errors.Join(err, fmt.Errorf(\n\t\t\t\t\"Self reference: %s\", VertexName(e.Source())))\n\t\t}\n\t}\n\n\treturn err\n}\n\n// Cycles reports any cycles between graph nodes.\n// Self-referencing nodes are not reported, and must be detected separately.\nfunc (g *AcyclicGraph) Cycles() [][]Vertex {","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/hashicorp/packer/blob/eb36e3c3e48a036f3e8cc94087636ee72e1303c9/internal/dag/dag.go#L24-L60","documentation":"AcyclicGraph.Validate detects strongly-connected components in the DAG; any component with more than one vertex is a dependency cycle. For each cycle it reports `Cycle: <v1, v2, ...>` and joins the errors, so callers like buildPrereqsDAG can see every cycle at once. A cycle means the dependency graph cannot be topologically ordered.","triggerScenarios":"buildPrereqsDAG adds edges A→B and B→A (or longer loops) between build prerequisites; Validate enumerates the SCC and emits this error naming the vertices in the cycle.","commonSituations":"Two components list each other as prerequisites; typo causing component A's prereq to resolve back to A through a chain; programmatically generated graphs adding both directions of an edge; HCL configs with mutually dependent build targets.","solutions":["Remove the edge that closes the loop — check the named vertices and break the mutual dependency","Use a multi-step build or artifact dependency (post-processor/builder output) instead of a direct prereq cycle","Trace the reported cycle list vertex-by-vertex to find the config entry that introduces the back-edge","If building the graph programmatically, add a guard that rejects an edge when a reverse-reachable path exists"],"exampleFix":"// before\ngraph.Add(a, b)\ngraph.Add(b, a) // Cycle: a, b\n// after\ngraph.Add(a, b) // single direction only","handlingStrategy":"validation","validationCode":"// Detect cycles before Validate\nfunc hasCycle(g *dag.AcyclicGraph) bool {\n    var cycles []verticies\n    // strongly-connected-component check, or simply:\n    return len(g.Cycles()) > 0\n}","typeGuard":null,"tryCatchPattern":"if err := g.Validate(); err != nil {\n    if strings.Contains(err.Error(), \"Cycle:\") {\n        return fmt.Errorf(\"prerequisite graph has a dependency cycle: %w\", err)\n    }\n    return err\n}","preventionTips":["Never add both directions of a dependency edge","Check Cycles() after graph construction in tests","Keep prerequisite declarations one-directional in config","Log the full vertex list from the Cycle error to locate the offending config"],"tags":["dag","dependency-cycle","graph"],"backgroundTag":"dependency-cycle","analyzedSha":"eb36e3c3e48a036f3e8cc94087636ee72e1303c9","analyzedAt":"2026-09-05T13:20:43.127Z","contentChangedAt":"2026-09-05T13:20:43.127Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}