Task Dependencies and Control Flow
Building the graph
Dependencies are set with a >> b, a.set_downstream(b), or lists: [a, b] >> c means both a and b must succeed before c. chain(a, b, c) and cross_downstream are helpers worth recognizing.
Control-flow operators shape execution: BranchPythonOperator (returns the task_id or list of ids to follow; skipped branches are marked skipped), ShortCircuitOperator (skips all downstream if the callable returns falsy), TriggerDagRunOperator, and LatestOnlyOperator.
Traps candidates hit
trigger_ruledefaults toall_success. With branching, the join task downstream of both branches will be skipped unless you settrigger_rule='none_failed_min_one_success'or'one_success'. This is the single most tested control-flow trap.all_doneruns regardless of upstream state, including failures;all_failedis its mirror.one_failedfires as soon as one upstream fails, without waiting for the rest.depends_on_past=Truemakes a task wait for the same task in the previous interval to succeed; combine withwait_for_downstreamcarefully.- Branch callables must return task ids that exist and are direct downstream; returning an unrelated id raises an error or silently skips everything.
Rule of thumb
Whenever a graph has a join after a branch, ask: "What trigger rule makes this join correct?" If any upstream may be skipped, all_success is wrong.
Worked example
branch >> [extract_a, extract_b] >> join, where join has trigger_rule='none_failed_min_one_success'. One branch runs, the other is skipped, and the join still executes because no upstream failed.