udsy19 opened a new pull request, #73470:
URL: https://github.com/apache/airflow/pull/73470

   `wait_for_past_depends_before_skipping` is documented to hold a 
`depends_on_past` task back — leave it in `none` state — instead of skipping 
it, whenever its own past-run dependency is not yet met. That contract is 
honored in exactly one place: `NotPreviouslySkippedDep`, the scheduler 
dependency check that re-evaluates a task recorded in a `SkipMixin` XCom 
payload and checks the `PAST_DEPENDS_MET` XCom before letting it be skipped.
   
   The problem: `SkipMixin.skip()` (used by `ShortCircuitOperator`) and 
`SkipMixin.skip_all_except()` (used by `BranchPythonOperator`) never route 
through that check for their own direct targets. Both raise 
`DownstreamTasksSkipped`, which the task-sdk supervisor turns into a `PATCH 
.../skip-downstream` call 
(`airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py::ti_skip_downstream`)
 that force-writes `state=SKIPPED` on the named task instances directly — no 
`depends_on_past`/`wait_for_past_depends_before_skipping` check anywhere on 
that path, only a guard against clobbering a RUNNING/SUCCESS/FAILED row. So a 
task with the flag set gets skipped immediately by its upstream 
short-circuit/branch, regardless of whether its own past run succeeded.
   
   Impact: contract-violation
   
   WHO reaches this / entry point: any DAG using the documented 
`wait_for_past_depends_before_skipping=True` + `depends_on_past=True` 
combination on a task that sits downstream of a `ShortCircuitOperator` or 
`BranchPythonOperator` — exactly the workflow the flag's own PR (#27710) was 
written for, and exactly apache/airflow#55146's own reproduction. Triggered by: 
the upstream `ShortCircuitOperator`/`BranchPythonOperator` deciding to skip 
that specific downstream task in the current DAG run (short-circuit condition 
false, or branch not taken). What they observe: the task is marked `SKIPPED` 
immediately, even when the previous DAG run's instance of that same task failed 
— `wait_for_past_depends_before_skipping` has no effect at all for this (very 
common) skip path, as reported.
   
   ## Fix
   
   `SkipMixin.skip()`/`skip_all_except()` already push the `XCOM_SKIPMIXIN_KEY` 
payload listing every affected task before deciding what to force-skip — that 
payload is what `NotPreviouslySkippedDep` reads when it later re-evaluates a 
task. Mapped tasks are already excluded from the immediate force-skip and left 
for that same check ("future mapped tasks have not been expanded yet and are 
handled by NotPreviouslySkippedDep"). This PR extends the same exclusion to any 
task with `wait_for_past_depends_before_skipping=True`: it is still recorded in 
the XCom payload (so `NotPreviouslySkippedDep` knows it should be skipped once 
its past depends are met), but it is no longer force-skipped by the direct 
`skip-downstream` write. `NotPreviouslySkippedDep` picks it up on the next 
scheduler pass and correctly holds it in `none` until `PAST_DEPENDS_MET` is 
true.
   
   ## Testing
   
   Two new tests in 
`task-sdk/tests/task_sdk/bases/test_skipmixin.py::TestSkipMixin`:
   
   - `test_skip_excludes_wait_for_past_depends_task` (the 
`ShortCircuitOperator` path via `skip()`)
   - `test_skip_all_except_excludes_wait_for_past_depends_task` (the 
`BranchPythonOperator` path via `skip_all_except()`)
   
   Negative control — reverting only 
`task-sdk/src/airflow/sdk/bases/skipmixin.py` to `upstream/main` and running 
the full test file:
   
   ```
   FAILED 
task-sdk/tests/task_sdk/bases/test_skipmixin.py::TestSkipMixin::test_skip_excludes_wait_for_past_depends_task
   FAILED 
task-sdk/tests/task_sdk/bases/test_skipmixin.py::TestSkipMixin::test_skip_all_except_excludes_wait_for_past_depends_task
   2 failed, 12 passed, 1 warning in 2.48s
   ```
   
   With the fix restored, the full file passes: `14 passed, 1 warning`. The 
other 12 pre-existing tests in the file are unmodified in behavior — they now 
pass an explicit `wait_for_past_depends_before_skipping=False` on their mock 
task fixtures, because without it the new filtering logic reads an 
auto-generated (truthy) `MagicMock` attribute from bare 
`MagicMock(spec=BaseOperator)` instances and incorrectly defers them too. That 
is a mocking artifact, not a production behavior change: real 
`BaseOperator`/`MappedOperator` instances always carry the real 
dataclass-defaulted `False`.
   
   closes: #55146
   
   Signed-off-by: Udaya Tejas <[email protected]>
   
   ---
   
   ##### Was generative AI tooling used to co-author this PR?
   
   - [X] Yes - Claude Code (Sonnet 5)
   
   Generated-by: Claude Code (Sonnet 5) following [the 
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to