hashicorp/packer · error
Cycle: %s
Error message
Cycle: %s
What it means
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.
Source
Thrown at internal/dag/dag.go:42
type DepthWalkFunc func(Vertex, int) error
func (g *AcyclicGraph) DirectedGraph() Grapher {
return g
}
// Validate validates the DAG. A DAG is valid if it has no cycles or self-referencing vertex.
func (g *AcyclicGraph) Validate() error {
// Look for cycles of more than 1 component
var err error
cycles := g.Cycles()
if len(cycles) > 0 {
for _, cycle := range cycles {
cycleStr := make([]string, len(cycle))
for j, vertex := range cycle {
cycleStr[j] = VertexName(vertex)
}
err = errors.Join(err, fmt.Errorf(
"Cycle: %s", strings.Join(cycleStr, ", ")))
}
}
// Look for cycles to self
for _, e := range g.Edges() {
if e.Source() == e.Target() {
err = errors.Join(err, fmt.Errorf(
"Self reference: %s", VertexName(e.Source())))
}
}
return err
}
// Cycles reports any cycles between graph nodes.
// Self-referencing nodes are not reported, and must be detected separately.
func (g *AcyclicGraph) Cycles() [][]Vertex {View on GitHub (pinned to eb36e3c3e4)
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
Example fix
// before graph.Add(a, b) graph.Add(b, a) // Cycle: a, b // after graph.Add(a, b) // single direction only
Defensive patterns
Strategy: validation
Validate before calling
// Detect cycles before Validate
func hasCycle(g *dag.AcyclicGraph) bool {
var cycles []verticies
// strongly-connected-component check, or simply:
return len(g.Cycles()) > 0
} Try / catch
if err := g.Validate(); err != nil {
if strings.Contains(err.Error(), "Cycle:") {
return fmt.Errorf("prerequisite graph has a dependency cycle: %w", err)
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of hashicorp/packer@eb36e3c3e4 (2026-09-05).
Data as JSON: /api/errors/e8cd9bbf90e7182d.
Report an issue: GitHub.