## TL;DR
Airflow refuses to load a DAG whose task dependencies form a cycle (A depends on B depends on A), because a cyclic DAG can never schedule. Find the loop with `airflow dags show` or by reading the dependency chain, break it by removing or reversing one edge, and the DAG parses again.

```text
airflow.exceptions.AirflowDagCycleException: cycle detected
```

## Use this when
- DAG parsing fails with AirflowDagCycleException
- An agent generated task dependencies with a loop
- The error appeared after adding a new dependency edge

## Not for this skill when
- The DAG file has an import error (thats the Python, not the graph)
- Tasks are missing (thats a definition problem)
- The scheduler wont pick up the DAG (thats scheduling)

## Steps

1. Render the DAG to see the structure:

```bash
airflow dags show my_dag --save /tmp/dag.png
```
Expected output: an image of the task graph. The cycle is usually visible as tasks feeding back into each other.

2. Trace the cycle in code. The classic agent-generated shape:

```python
# broken: extract, transform, load wired in a loop
task_a.set_downstream(task_b)
task_b.set_downstream(task_c)
task_c.set_downstream(task_a)  # this edge closes the loop
```
Expected output: you can name the tasks in the loop. Comment out the suspect edge and re-parse.

3. Break the cycle by removing or reversing one edge:

```python
task_a.set_downstream(task_b)
task_b.set_downstream(task_c)
# trigger logic that needed the loop goes in a sensor or a separate DAG
```
Expected output: `airflow dags test my_dag 2026-01-01` parses without the cycle error. Loops that meant "rerun when done" belong in sensors, not in DAG edges.

4. Verify with the DAG test command:

```bash
airflow dags test my_dag 2026-01-01
```
Expected output: the DAG runs to completion. The cycle check runs at parse time, so a clean parse means the fix held.

## Variant phrasings

### cycle detected but the code looks acyclic
Check task groups and dynamic task mapping: mapped tasks or a TaskGroup can introduce edges you didnt write literally. Print the rendered graph.

### cycle after adding a trigger rule dependency
`TriggerRule` doesnt create edges, but the set_downstream call you added alongside it might. The rule and the edge are separate things.

## Why it happens
Airflow schedules by topological order, which only exists for acyclic graphs. A cycle means no task can ever run first, so Airflow rejects the DAG at parse time rather than deadlocking at runtime. Agents create cycles when they translate "do X then Y then X again" style instructions literally into dependencies instead of using sensors or separate DAG runs.

## Edge cases
- Self-dependencies (a task set downstream of itself) also count as cycles; check for those in generated code.
- SubDAGs and TaskGroups can hide cycles inside their internal wiring; expand them when tracing.
- The cycle check runs on every parse, so a DAG that "worked yesterday" with a cycle never actually worked; something else changed to trigger the re-parse.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_iLGjPVqIToZIZZtrTEZ0yw
