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

  1. 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.
  2. Use terraform graph to visualize the dependency graph and locate the cycle visually.
  3. Decouple resources by splitting one into a data source or by removing a cross-reference that closes the loop.
  4. 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

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


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/4f59418903def8df. Report an issue: GitHub.