TL;DR: Two resources (or modules) reference each other, so tofu can't order them. Visualize with `tofu graph`, find the loop, and break it: replace one side with a static value, split the resource, or pass the value as a variable instead of reading it back.

```text
Error: Cycle: module.a.aws_x.foo, module.b.aws_y.bar
```

## Steps

1. Visualize: `tofu graph | dot -Tsvg > graph.svg` and open it.
   Expected: you can SEE the loop instead of squinting at HCL.
2. Identify the weakest edge: which reference can become a static value or an input variable?
   Expected: one reference is incidental (a tag, a name) rather than structural.
3. Break it. Common patterns:
   - Self-referential security group: split the ingress rule into a separate `aws_security_group_rule` resource instead of inline `ingress` blocks.
   - Module A reads module B's output while B reads A's: pass the shared value in as a variable to both from the parent.
   - Tag/name computed from the other resource: hardcode it or derive both from the same input.
   Expected: the graph is acyclic.
4. Re-run `tofu plan`.
   Expected: the cycle error is gone.

## When this applies

- `Error: Cycle:` listing two or more addresses.
- After wiring two modules/resources to each other's outputs.

## When it doesn't apply

- `Error: Reference to undeclared resource`: a dangling reference, not a loop.
- Cycles through `depends_on` you added "to force ordering": remove the unnecessary edge; that's the loop.

## Tool versions

All OpenTofu versions.

## Why it happens

Tofu builds a dependency graph from references and walks it in topological order. A cycle means no valid order exists, so it fails fast and names the loop rather than deadlocking or guessing.

## Edge cases

- Indirect cycles through three or more resources are the ones nobody spots by reading; `tofu graph` is the tool, not eyeballing.
- `depends_on` edges count the same as reference edges; an unnecessary `depends_on` added "just to be safe" is a common accidental loop-closer.