# Trigger Rules

***

### **Trigger Rules**

**Trigger rules** are the logical conditions Airflow uses to decide when a downstream task should start based on the status of its parents. By default, Airflow assumes you want every preceding task to succeed before moving forward, but in real-world data engineering, you often need more flexibility.

Here is the breakdown of the trigger rules from the table you provided.

***

#### The "Standard" Rules

These are used for linear pipelines where one failure should stop the whole chain.

* `all_success` **(Default)**: The task only triggers if every single parent task finishes successfully. This is the standard for a normal, healthy workflow.
* `all_failed`: Only triggers if all parents have failed. Use this for specific error-handling code meant to clean up resources after a total failure.
* `all_done`: Triggers once all parents finish, no matter their state (success, failed, or skipped). This is ideal for "cleanup" tasks, like shutting down a database cluster or a virtual machine.

#### The "Speed" Rules (One-of)

These allow the pipeline to react faster without waiting for every parent to finish.

* `one_failed`: Triggers as soon as a single parent fails. You don't have to wait for the other parents to finish; this is great for immediate rollback notifications.
* `one_success`: Triggers as soon as at least one parent succeeds. Use this for notifications or computations where you only need one result to become available to proceed.
* `one_done`: Triggers if at least one parent completes, whether it succeeded or failed.

#### The "Branching & Join" Rules

These are the most important rules for the **Task Branching** we discussed earlier.

* `none_failed`: Triggers if no parents failed, meaning they were either successful or skipped. This is commonly used to join branches in a DAG.
* `none_failed_min_one_success`: This is the "safe join" for branching. It triggers if no parents failed and at least one actually succeeded. This prevents a join task from running if every single branch was skipped.
* `all_skipped`: Triggers only if every parent was skipped. You can use this to execute a "fallback" logic if the main path wasn't taken.

#### Special Use Cases

* `always`: Triggers regardless of what happened upstream. This is primarily used for testing.
* `none_skipped`: Triggers if no parents were skipped. It will run if all parents were either successful or failed, but ignores the actual results.

***

#### Comparison Summary

| **Use Case**           | **Recommended Rule**          |
| ---------------------- | ----------------------------- |
| **Normal Pipeline**    | `all_success`                 |
| **Cleanup/Tear Down**  | `all_done`                    |
| **Joining a Branch**   | `none_failed_min_one_success` |
| **Error Notification** | `one_failed`                  |

***

### Example

To implement a "Join" task using the TaskFlow API, you apply the `trigger_rule` argument directly within the `@task` decorator. This ensures that even if one branch of your DAG is skipped, the final task still knows when it is safe to proceed.

#### Implementing the "Safe Join" with TaskFlow

When you have a branch, one path becomes **Success** and the other becomes **Skipped**. Because the default rule is `all_success`, a downstream task would normally see that "Skipped" parent and decide to skip itself too. To fix this, we use `none_failed_min_one_success`.

```python
from airflow.decorators import dag, task
from airflow.utils.trigger_rule import TriggerRule
import pendulum

@dag(start_date=pendulum.datetime(2026, 1, 1), schedule="@daily", catchup=False)
def branching_join_example():

    @task.branch
    def decide_branch():
        # Logic to choose a path
        return "path_a"

    @task
    def path_a():
        return "Data from Path A"

    @task
    def path_b():
        return "Data from Path B"

    # We apply the specific trigger rule here to join the branches
    @task(trigger_rule=TriggerRule.NONE_FAILED_MIN_ONE_SUCCESS)
    def join_and_summarize(data):
        print(f"Processing: {data}")

    # Define the flow
    branch_choice = decide_branch()
    task_a = path_a()
    task_b = path_b()

    # The branching task points to both potential paths
    branch_choice >> [task_a, task_b]
    
    # The join task waits for either a or b (whichever wasn't skipped)
    join_and_summarize([task_a, task_b])

branching_join_example()
```

***

#### Why this rule is the "Gold Standard" for Joins

* `none_failed`: This would trigger if parents were successful or skipped. However, if *every* parent was skipped (for example, if a `LatestOnlyOperator` skipped the whole DAG), this task would still run.
* `none_failed_min_one_success`: This is smarter. It triggers if no parents failed AND at least one actually succeeded. This ensures your "Join" task only runs if there is actually data to process from at least one branch.

#### Summary of common Join Rules

| **Rule**                      | **Behavior**                                    | **Best Used For...**                               |
| ----------------------------- | ----------------------------------------------- | -------------------------------------------------- |
| `all_success`                 | All parents must be Green.                      | Standard linear pipelines.                         |
| `none_failed_min_one_success` | No Red parents, at least one Green.             | Merging branches where one side is Pink (Skipped). |
| `all_done`                    | Ignores colors; runs when everyone is finished. | Cleanup tasks (e.g., closing a database).          |
| `one_failed`                  | Triggers as soon as any parent turns Red.       | Alerting or emergency rollbacks.                   |

***
