Uh oh!
There was an error while loading. Please reload this page.
Fix multiple_outputs no-op on deferrable KubernetesPodOperator - #67226
Conversation
bc4bde8 to
70cfe3aCompareKubernetesPodOperator(do_xcom_push=True, multiple_outputs=True, deferrable=True) silently failed to fan out the sidecar's return.json dict into per-key XComs — only `return_value` was published. Downstream tasks subscripting a key (operator.output["foo"] resolving to xcom_pull(key="foo")) got None at runtime with no error. Root cause: trigger_reentry pushed return_value manually inside a finally block and never returned the value to the task runner, so the runner's _push_xcom_if_needed (the code that honors multiple_outputs and fans the dict out) was bypassed. The sync execute_sync path already returns the result for the runner to handle (pod.py:760); this aligns trigger_reentry with that same contract. The failure-path manual push is preserved by moving it inside the event["status"] != "success" branch above the raise — partial sidecar output is still surfaced in XCom when the pod fails, and the behavior is now strictly better: the push happens even when the subsequent _clean call raises (previously the in-finally push was unreachable in that case). Fixesapache#67224 Co-authored-by: Cursor <cursoragent@cursor.com>
70cfe3a to
b3c1666CompareCo-authored-by: Cursor <cursoragent@cursor.com>
jscheffl
left a comment
There was a problem hiding this comment.
Thanks for the improvement. So it improves on one end but actually then with the fix leaves the two code path's in an un-fixed state. When either sync or sync execution fails and xcom is pushed it still skips handling multiple outputs.
Can you make it completely consistent?
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Pull request overview
Fixes deferrable KubernetesPodOperator to return the XCom sidecar payload on success so the task runner can apply _push_xcom_if_needed (including multiple_outputs fan-out), aligning behavior with the sync execution path.
Changes:
- Update
trigger_reentryto returnxcom_sidecar_outputon success (and stop manually pushingreturn_valuein the success path). - Preserve failure-path behavior by manually pushing
return_valuebefore raising, ensuring partial sidecar output is still captured even if cleanup errors. - Adjust and extend unit tests to reflect/lock in the new return-vs-push contract.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/operators/pod.py | Return sidecar output on successful deferrable re-entry so task runner can handle XCom push and multiple_outputs fan-out; keep manual failure push. |
providers/cncf/kubernetes/tests/unit/cncf/kubernetes/operators/test_pod.py | Update existing async KPO tests for the new contract and add a test asserting trigger_reentry returns sidecar output when multiple_outputs=True. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Address review on apache#67226: the previous commit fixed the success path on deferrable KPO but left both sync and async failure paths still pushing only ``return_value``, silently dropping ``multiple_outputs`` fan-out. Add an operator-local ``_push_xcom_with_fan_out`` helper that mirrors the task runner's ``_push_xcom_if_needed`` and call it from both failure-path manual pushes (sync ``post_complete_action`` and async ``trigger_reentry``). All four code paths — sync success/failure, async success/failure — now honour ``multiple_outputs`` consistently. Promoting ``_push_xcom_if_needed`` to a public task-SDK helper would let other operators that manually push XCom on failure paths reuse the same logic and would be a cleaner long-term fix. Left as a follow-up since manual ``XCOM_RETURN_KEY`` pushes are essentially KPO-specific today (grep across all providers turns up only one other operator) and the local helper keeps this PR's blast radius matched to its scope. Tests: - New ``test_xcom_push_failed_pod_fans_out_for_multiple_outputs`` covers the sync failure path. - New ``test_async_trigger_reentry_failure_fans_out_for_multiple_outputs`` covers the async failure path. Also incorporate review nits: drop unnecessary comments, switch ``is`` to ``==`` for dict equality checks, and trim the historical context from the new test docstring (moved to the PR description). Co-authored-by: Cursor <cursoragent@cursor.com>
paultmathew
commented
May 21, 2026
Thanks for the review @jscheffl. Pushed a fix with an operator-local _push_xcom_with_fan_out helper used by both sync (post_complete_action) and async (trigger_reentry) failure-path pushes — so all four code paths now honour multiple_outputs consistently. Added matching failure-path tests for both modes. I considered promoting _push_xcom_if_needed to a public task-SDK helper as you mentioned. Manual XCOM_RETURN_KEY pushes from operator code are essentially KPO-specific today (grep across all providers only turns up one other place), so I kept the helper local to keep this PR focused — happy to file a follow-up issue for the wider task-SDK change if you'd prefer. |
Uh oh!
There was an error while loading. Please reload this page.
jscheffl
commented
May 21, 2026
Looks almost good. One final (hopefully final) finding. Can you close the comments resolved? If static checks are fixed alongside one comment from me then I think it is good to merge |
Address review from @jscheffl on apache#67226: the previous ``if not value:`` guard would skip the manual XCom push for any falsy-but-non-None payload (``False``, ``0``, ``""``, ``[]``, ``{}``) — all of which are legitimate sidecar outputs that downstream tasks may rely on. Switch to ``is None`` so only the "nothing extracted" case is skipped, matching the task runner's ``_push_xcom_if_needed`` semantics (which also guards on ``xcom_value is None``). Also fix D213 docstring style flagged by ruff (multi-line summary should start on the second line). Co-authored-by: Cursor <cursoragent@cursor.com>
Uh oh!
There was an error while loading. Please reload this page.
Summary
KubernetesPodOperator(do_xcom_push=True, multiple_outputs=True, deferrable=True)silently failed to fan out the sidecar'sreturn.jsondict into per-key XComs — onlyreturn_valuewas published. Downstream tasks subscripting a key gotNoneat runtime with no error.Root cause:
trigger_reentrypushedreturn_valuemanually inside afinallyblock and never returned the value to the task runner, so the runner's_push_xcom_if_needed(the code that honorsmultiple_outputsand fans the dict out) was bypassed.The sync path's
execute_syncalready returnsresult(pod.py:760). This aligns the deferrable path with the same contract.Behaviour change
trigger_reentryreturnsxcom_sidecar_output. The task runner pushesreturn_valueand, whenmultiple_outputs=True, also fans the dict out into per-key XComs.xcom_push(XCOM_RETURN_KEY, ...)moves into theevent["status"] != "success"branch, above theraise. Partial sidecar output is still surfaced in XCom, and the push now happens even when_cleansubsequently raises (strict improvement — previously the in-finallypush was unreachable in that case).multiple_outputsfan-out is deliberately not applied on the failure path: downstream tasks that would consume per-key XComs are inUPSTREAM_FAILEDanyway. The single-keyreturn_valuepush on failure mirrors the sync path's failure-time push atpod.py:1198("Ensure that existing XCom is pushed even in case of failure").History
The deferred-path manual push was introduced by #58488 and refined by #58998. #58488 was titled "Deferred KubernetesPodOperator pushes XCom on successful execution" — the intent was success-path only. #58998 then removed a
returnfrom thefinallyblock (returning fromfinallysilently swallows exceptions) and replaced it withxcom_push, preserving the symptom on both paths. The success-path fan-out was lost in that translation; this PR restores it.Closes
Fixes#67224
Tests
Updated
test_async_kpo_wait_termination_before_cleanup_on_successandtest_async_kpo_wait_termination_before_cleanup_on_failureto reflect the new contract.Added
test_async_trigger_reentry_returns_sidecar_output_for_multiple_outputsto lock in the success-path return value withmultiple_outputs=True.The end-to-end
_push_xcom_if_neededfan-out behavior is already covered by task-SDK unit tests.