airflow.exceptions.AirflowDagCycleException: cycle detected
Fixes Airflow AirflowDagCycleException when a DAG contains a dependency cycle. Use when DAG parsing fails with cycle detected, when an agent wires tasks in a loop, or after refactoring task dependencies. Not for import errors, for missing tasks, or for scheduler issues.
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.
airflow.exceptions.AirflowDagCycleException: cycle detectedUse 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
- Render the DAG to see the structure:
airflow dags show my_dag --save /tmp/dag.pngExpected output: an image of the task graph. The cycle is usually visible as tasks feeding back into each other.
- Trace the cycle in code. The classic agent-generated shape:
# 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 loopExpected output: you can name the tasks in the loop. Comment out the suspect edge and re-parse.
- Break the cycle by removing or reversing one edge:
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 DAGExpected 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.
- Verify with the DAG test command:
airflow dags test my_dag 2026-01-01Expected 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