hashicorp/terraform · critical
Cycle: %s
Error message
Cycle: %s
What it means
Returned by AcyclicGraph.Validate() (dag.go:238) when the graph contains a strongly connected component of length > 1 — i.e. a genuine cycle where vertices depend on each other in a loop. The message lists the vertex names forming the cycle (reversed and pivoted for readability). An AcyclicGraph must be acyclic to be walkable, so this is a hard validation failure.
Source
Thrown at internal/dag/dag.go:238
// compare lexically.
first := 0
for i := 1; i < len(cycleStr); i++ {
if len(cycleStr[i]) < len(cycleStr[first]) {
first = i
continue
}
if len(cycleStr[i]) == len(cycleStr[first]) && cycleStr[i] < cycleStr[first] {
first = i
}
}
// pivot the slice around our new first index
if first > 0 {
cycleStr = append(cycleStr[first:], cycleStr[:first]...)
}
err = errors.Join(err, fmt.Errorf(
"Cycle:\n %s", strings.Join(cycleStr, "\n "),
))
}
}
// Look for cycles to self
for _, e := range g.Edges() {
if e.Source == e.Target {
err = errors.Join(err, fmt.Errorf(
"Self reference: %s", e.Source.Name()))
}
}
return err
}
// Cycles reports any cycles between graph nodes.
// Self-referencing nodes are not reported, and must be detected separately.View on GitHub (pinned to c9def3e214)
Solutions
- Read the cycle path in the message (each line is a vertex name) — these resources mutually depend on each other; break the loop by removing one depends_on or restructuring the references.
- Use terraform graph to visualize the dependency graph and locate the cycle visually.
- Decouple resources by splitting one into a data source or by removing a cross-reference that closes the loop.
- Check for accidental self-referential module/resource includes or a depends_on that points back upstream.
Example fix
// before — resource_a depends_on [resource_b] AND resource_b depends_on [resource_a]
// after — break the cycle: remove one direction
resource "x" "a" { /* no depends_on to b */ }
resource "x" "b" { depends_on = [x.a] } Defensive patterns
Strategy: validation
Validate before calling
// Detect cycles before Validate() surfaces them as plan errors
func hasCycle(g *dag.AcyclicGraph) bool {
return len(g.Cycles()) > 0
}
// Cycles() returns the vertex loops; inspect them to break dependencies proactively Type guard
func isAcyclic(g *dag.AcyclicGraph) bool {
return len(g.Cycles()) == 0
} Try / catch
if err := graph.Validate(); err != nil {
log.Printf("[ERROR] dependency graph invalid: %v", err)
return err // surface the cycle path to the user for resolution
} Prevention
- Avoid bidirectional depends_on between resources.
- Use 'terraform graph' to visualize dependencies and catch loops early.
- If two resources truly need each other's outputs, split one into a data source.
When it happens
Trigger: Calling Validate() on an AcyclicGraph (used for Terraform's resource dependency graph) after adding edges that form a loop — e.g. resource A depends on B, B depends on C, C depends on A. Terraform runs this during plan when resolving resource dependencies.
Common situations: Circular resource dependencies in configuration (e.g. two resources each reference an attribute of the other via depends_on or interpolation). A provider or module that wires up graph edges forming a loop. Importing resources whose recorded dependencies loop. Incorrect use of depends_on creating an artificial cycle.
Related errors
- Self reference: %s
- identity schema not found for type %s
- resource identity schema not found for type %q
- resource schema not found for type %q
- list resource schema not found for type %q
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/4f59418903def8df.
Report an issue: GitHub.