Skip to content

Fix dynamic dag_id resolution in TriggerDagRunOperator links#56973

Open
hwang-cadent wants to merge 1 commit intoapache:mainfrom
hwang-cadent:fix-46402-trigger-dagrun-dynamic-dag-id-links
Open

Fix dynamic dag_id resolution in TriggerDagRunOperator links#56973
hwang-cadent wants to merge 1 commit intoapache:mainfrom
hwang-cadent:fix-46402-trigger-dagrun-dynamic-dag-id-links

Conversation

@hwang-cadent
Copy link
Copy Markdown
Contributor

Fixes #46402 - Dynamic dag_id resolution in TriggerDagRunOperator links

Description

This PR addresses the issue where TriggerDagRunOperator links fail to work correctly when the dag_id is dynamically determined at runtime (e.g., from XCom values or complex templates). The current implementation only uses the static trigger_dag_id attribute, which doesn't reflect the actual resolved dag_id when it's determined dynamically.

Changes

  • Added XCOM_DAG_ID constant to store the resolved dag_id in XCom during task execution
  • Modified TriggerDagRunLink.get_link() to prioritize XCom values over static operator attributes
  • Updated both Airflow 2.x (_trigger_dag_af_2) and 3.x (_trigger_dag_af_3) execution paths to push the resolved dag_id to XCom
  • Added comprehensive test coverage for dynamic dag_id scenarios in both Airflow versions
  • Maintained full backward compatibility with existing static dag_id usage
  • Added proper error handling for test environments where supervisor communication isn't available

Testing

  • All existing tests continue to pass (10 tests passed, 26 skipped for Airflow 2.x compatibility)
  • Added new test cases for dynamic dag_id resolution in both Airflow 2.x and 3.x
  • Verified backward compatibility with static dag_id usage
  • Tested error handling for environments without supervisor communication
  • Ensured XCom fallback behavior works correctly

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

^ Add meaningful description above
Read the Pull Request Guidelines for more information.
In case of fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
In case of a new dependency, check compliance with the ASF 3rd Party License Policy.
In case of backwards incompatible changes please leave a note in a newsfragment file, named {pr_number}.significant.rst or {issue_number}.significant.rst, in airflow-core/newsfragments.

@boring-cyborg
Copy link
Copy Markdown

boring-cyborg Bot commented Oct 21, 2025

Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide (https://github.com/apache/airflow/blob/main/contributing-docs/README.rst)
Here are some useful points:

  • Pay attention to the quality of your code (ruff, mypy and type annotations). Our prek-hooks will help you with that.
  • In case of a new feature add useful documentation (in docstrings or in docs/ directory). Adding a new operator? Check this short guide Consider adding an example DAG that shows how users should use it.
  • Consider using Breeze environment for testing locally, it's a heavy docker but it ships with a working Airflow and a lot of integrations.
  • Be patient and persistent. It might take some time to get a review or get the final approval from Committers.
  • Please follow ASF Code of Conduct for all communication including (but not limited to) comments on Pull Requests, Mailing list and Slack.
  • Be sure to read the Airflow Coding style.
  • Always keep your Pull Requests rebased, otherwise your build might fail due to changes not related to your commits.
    Apache Airflow is a community-driven project and together we are making it better 🚀.
    In case of doubts contact the developers at:
    Mailing List: [email protected]
    Slack: https://s.apache.org/airflow-slack

@hwang-cadent hwang-cadent marked this pull request as draft November 2, 2025 14:52
@hwang-cadent hwang-cadent marked this pull request as ready for review November 2, 2025 14:53
@hwang-cadent
Copy link
Copy Markdown
Contributor Author

@XD-DENG @ashb Could you please review this PR?

This PR fixes dynamic dag_id resolution in TriggerDagRunOperator links (Fixes #46402). The changes involve:

  • XCom usage for storing resolved dag_id
  • Link generation updates
  • Core operator functionality

Thank you!

Comment thread providers/standard/src/airflow/providers/standard/operators/trigger_dagrun.py Outdated
@hwang-cadent hwang-cadent requested a review from uranusjr November 4, 2025 21:03
@eladkal eladkal changed the title Fix 46402 trigger dagrun dynamic dag id links Fix dynamic dag_id resolution in TriggerDagRunOperator links Nov 6, 2025
@hwang-cadent hwang-cadent force-pushed the fix-46402-trigger-dagrun-dynamic-dag-id-links branch 6 times, most recently from 76a3ed7 to 331face Compare November 12, 2025 00:58
@jscheffl jscheffl force-pushed the fix-46402-trigger-dagrun-dynamic-dag-id-links branch from bfbe526 to 735f5af Compare November 13, 2025 21:10
@hwang-cadent hwang-cadent force-pushed the fix-46402-trigger-dagrun-dynamic-dag-id-links branch 9 times, most recently from d1598cd to 2c20224 Compare November 16, 2025 15:12
@github-actions
Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 5 days if no further activity occurs. Thank you for your contributions.

@github-actions github-actions Bot added the stale Stale PRs per the .github/workflows/stale.yml policy file label Jan 11, 2026
@hwang-cadent hwang-cadent force-pushed the fix-46402-trigger-dagrun-dynamic-dag-id-links branch 2 times, most recently from 0750c6b to 7f284d3 Compare January 12, 2026 15:21
hwang-cadent added a commit to hwang-cadent/airflow that referenced this pull request Feb 4, 2026
Resolve conflicts for PR apache#56973 (Fix dynamic dag_id resolution in TriggerDagRunOperator links):
- serialized_objects.py: keep main structure, re-apply ARG_NOT_SET deserialization fix for _date fields
- trigger_dagrun.py: drop openlineage_inject block to match main
- test_trigger_dagrun.py: keep main imports; keep execute_complete tests; drop openlineage tests
- test_task_runner.py: take main's imports, DagBag, listener fixtures, and test variants
Copy link
Copy Markdown
Member

@pierrejeambrun pierrejeambrun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something probably went wrong in the rebase process. The change set is huge.

Do you mind fixing the branch please so we can move forward.

@hwang-cadent
Copy link
Copy Markdown
Contributor Author

Something probably went wrong in the rebase process. The change set is huge.

Do you mind fixing the branch please so we can move forward.

Rebased on the latest apache/main and cleaned up. The change set now includes only the 4 PR-related files). Ready for review. Thank you for your patience.

@potiuk
Copy link
Copy Markdown
Member

potiuk commented Feb 15, 2026

@dabla @uranusjr ?

Copy link
Copy Markdown
Contributor

@dabla dabla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good to me reviewed it from mobile though so I might have missed something.

Comment thread airflow-core/src/airflow/serialization/serialized_objects.py Outdated
Comment thread airflow-core/src/airflow/serialization/serialized_objects.py Outdated
Copy link
Copy Markdown
Contributor

@dabla dabla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good to me apart from question if this change should also not be directly tested in test_serialized_objects instead of relying on indirect tests?

@hwang-cadent
Copy link
Copy Markdown
Contributor Author

Looking good to me apart from question if this change should also not be directly tested in test_serialized_objects instead of relying on indirect tests?

Good point. Currently tested indirectly through TriggerDagRunOperator tests. A direct unit test in test_serialized_objects.py for _deserialize_field_value() handling ARG_NOT_SET for date fields would be clearer. Should I add this test, or would you prefer to handle it in a follow-up?

@dabla
Copy link
Copy Markdown
Contributor

dabla commented Mar 3, 2026

Looking good to me apart from question if this change should also not be directly tested in test_serialized_objects instead of relying on indirect tests?

Good point. Currently tested indirectly through TriggerDagRunOperator tests. A direct unit test in test_serialized_objects.py for _deserialize_field_value() handling ARG_NOT_SET for date fields would be clearer. Should I add this test, or would you prefer to handle it in a follow-up?

It think it would be better even though it's indirectly tested through TriggerDagRunOperator tests. So yes would add case in test_serialized_objects.

@hwang-cadent
Copy link
Copy Markdown
Contributor Author

Looking good to me apart from question if this change should also not be directly tested in test_serialized_objects instead of relying on indirect tests?

Good point. Currently tested indirectly through TriggerDagRunOperator tests. A direct unit test in test_serialized_objects.py for _deserialize_field_value() handling ARG_NOT_SET for date fields would be clearer. Should I add this test, or would you prefer to handle it in a follow-up?

It think it would be better even though it's indirectly tested through TriggerDagRunOperator tests. So yes would add case in test_serialized_objects.

Thanks for the feedback. I've added a direct test case test_deserialize_field_value_with_arg_not_set_for_date_fields in test_serialized_objects.py to verify that _deserialize_field_value returns NOTSET for ARG_NOT_SET date fields. This complements the indirect tests through TriggerDagRunOperator and improves test coverage.
The test verifies the behavior for date fields like logical_date, start_date, and end_date when they contain ARG_NOT_SET values.

)
# Try to get the resolved dag_id from XCom first (for dynamic dag_ids)
trigger_dag_id = XCom.get_value(ti_key=ti_key, key=XCOM_DAG_ID)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: if not trigger_dag_id: uses a truthiness check. If XCom.get_value returns an empty string "" (unlikely but possible), this would incorrectly fall through to the operator attribute fallback.

Should be:
if trigger_dag_id is None:

Copy link
Copy Markdown
Member

@potiuk potiuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core idea of storing the resolved trigger_dag_id in XCom for dynamic link resolution is sound and addresses a real user-facing bug. However, there are several issues that should be addressed before merging:

  1. Architecture inconsistency in AF3: The trigger_dag_id XCom is pushed inside the operator's _trigger_dag_af_3() (before DagRunTriggerException is raised), while trigger_run_id is pushed in task_runner._handle_trigger_dag_run() (after successful trigger). This creates inconsistent state on trigger failure.
  2. Bug in get_link fallback check: Uses truthiness (if not trigger_dag_id) instead of identity (if trigger_dag_id is None).
  3. Separate concerns bundled together: The serialized_objects.py deserialization fix for ARG_NOT_SET on _date fields is a distinct bug and should ideally be a separate PR for cleaner review and bisectability.
  4. Test assertions weakened: Ordered call verification was replaced with unordered assert_any_call, losing important ordering guarantees.

potiuk

This comment was marked as duplicate.

trigger_dag_id = XCom.get_value(ti_key=ti_key, key=XCOM_DAG_ID)

# Fallback to operator attribute and rendered fields if not in XCom
if not trigger_dag_id:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should become as @potiuk pointed out:

if trigger_dag_id is not None:

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@potiuk
Copy link
Copy Markdown
Member

potiuk commented Apr 22, 2026

@hwang-cadent — This PR has new commits since the last review requesting changes from @potiuk. If you believe you've addressed the feedback, please ping the reviewer(s) to request a re-review. Thanks!


Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you.

@hwang-cadent
Copy link
Copy Markdown
Contributor Author

@potiuk @dabla — addressed the review feedback in 93beb9697f and split out the serialization concern as you suggested:

Comments 1, 2 and 4 (in 93beb9697f on this PR):

  1. AF3 architecture inconsistency — Moved XCOM_DAG_ID push out of _trigger_dag_af_3() and into task_runner._handle_trigger_dag_run() next to trigger_run_id. Both XComs are now written together only after a successful trigger, so XCom state stays consistent if the trigger fails.
  2. get_link fallback check — Changed if not trigger_dag_id: to if trigger_dag_id is None: so any explicit value pushed to XCom is honored.
  3. Ordered call assertions — Restored assert_has_calls with the full ordered call list in test_handle_trigger_dag_run_wait_for_completion (and added the new SetXCom("trigger_dag_id") to test_handle_trigger_dag_run).

Comment 3 (separate PR):

  1. Bundled serialization fix — Agreed. Split out as a standalone PR: Fix ARG_NOT_SET deserialization for _date fields in OperatorSerialization #66564 (Fix ARG_NOT_SET deserialization for _date fields in OperatorSerialization). Once that lands I'll rebase this PR on main and drop the serialized_objects.py / test_serialized_objects.py hunks from here.

Could you take another look when you get a chance? Thank you!

@hwang-cadent
Copy link
Copy Markdown
Contributor Author

Rebased on the latest apache/main (which had a meaningful refactor adding _validate_datetime_param and run_after support to TriggerDagRunOperator) and squashed the branch into a single clean commit (d1a826d9f8).

Conflict resolution highlights:

Final scope of this PR (4 files, +138 / -22):

  • providers/standard/src/airflow/providers/standard/operators/trigger_dagrun.py: add XCOM_DAG_ID constant, push it from _trigger_dag_af_2, and prefer it in TriggerDagRunLink.get_link() (with is None identity check).
  • task-sdk/src/airflow/sdk/execution_time/task_runner.py: push trigger_dag_id next to trigger_run_id in _handle_trigger_dag_run after a successful trigger.
  • providers/standard/tests/unit/standard/operators/test_trigger_dagrun.py: switch existing AF3 link test to mock XCom.get_value, add test_extra_operator_link_with_dynamic_dag_id for both AF2 and AF3.
  • task-sdk/tests/task_sdk/execution_time/test_task_runner.py: extend ordered assert_has_calls in both test_handle_trigger_dag_run and test_handle_trigger_dag_run_wait_for_completion with the new SetXCom("trigger_dag_id") step.

PR is now mergeable: true against main. Could you take another pass when you have a chance, @potiuk @dabla? Thank you!

The operator link previously read ``trigger_dag_id`` only from the
operator attribute (and from rendered template fields on AF2). When the
``dag_id`` is resolved at runtime — for example from an XCom pull or a
non-trivial template — the static attribute does not reflect the actual
DAG that was triggered, so the "Triggered DAG" link in the UI points to
the wrong (or non-existent) DAG.

This change stores the resolved ``trigger_dag_id`` in XCom alongside
``trigger_run_id`` after a successful trigger, and updates
``TriggerDagRunLink.get_link()`` to prefer that XCom value before
falling back to the operator attribute / rendered fields.

Notes:

- For Airflow 3.x the XCom is pushed in
  ``task_runner._handle_trigger_dag_run`` (next to ``trigger_run_id``)
  so both values share the same lifecycle and are not written when the
  trigger fails.
- For Airflow 2.x the XCom is pushed in ``_trigger_dag_af_2`` after the
  triggered ``DagRun`` is created.
- ``get_link`` uses an identity check (``trigger_dag_id is None``)
  rather than truthiness so any explicit value pushed to XCom is
  honored.

Tests:

- New ``test_extra_operator_link_with_dynamic_dag_id`` cases (AF2 & AF3)
  verifying the link uses the resolved ``dag_id`` from XCom.
- Existing AF3 ``test_extra_operator_link`` updated to mock
  ``XCom.get_value`` (matches the actual call site).
- ``test_handle_trigger_dag_run`` and
  ``test_handle_trigger_dag_run_wait_for_completion`` updated with the
  new ``SetXCom("trigger_dag_id")`` call in the expected ordered
  sequence, keeping ``assert_has_calls`` ordering guarantees.

Fixes apache#46402
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core area:serialization backport-to-v3-2-test Mark PR with this label to backport to v3-2-test branch ready for maintainer review Set after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancement: Support Dynamic dag_id Resolution in TriggerDagRunOperator for Clickable Links

9 participants