All of cases 1, 2 and 3 fall under “this is just Python”. In case 1 and 2 task2 
is never even instantiated; it will not be part of the dag. 

Case 5: Returning a non task type does nothing — Airflow should continue to 
ignore it.


Returning multiple, such as `return [t1, t2]` should be treated as case 5, a 
non Task return type; as the entire point of the “return value of a DAG” needs 
to be a single return value as that is what the API does; so if you do have a 
use case that needs “multiple” task’s in a single task I think the 
recommendation should be put a final task on that combines them so that there 
is a single XCom value to return.



> On 19 Jun 2026, at 13:55, Shahar Epstein <[email protected]> wrote:
> 
> +1 for the return syntax (which we could also utilize for returning results
> of multiple task instances, e.g., `return [t1, t2]` or `{"t1": t1, "t2":
> t2}`),
> but with some caution.
> 
> By using the `return` keyword, we change the semantics from actually
> terminating the flow to marking a DAG result,
> so we need to consider related edge cases.
> For example
> 
> 1.A return statement in the middle of the function:
> 
> ```
> @dag
> def my_dag():
>    t1 = task1()
>    return t1
>    t2 = task2(t1)
> ```
> 
> 2. Multiple returns:
> ```
> @dag
> def my_dag():
>    t1 = task1()
>    return t1
>    t2 = task2(t1)
>    return t2
> ```
> 
> 3. Conditions:
> ```
> ```
> @dag
> def my_dag():
>    t1 = task1()
>    t2 = task2(t1)
>    return t2 if SOME_FLAG else t1
> ```
> 4. Dynamic task mapping
> 5. Returning non-task types, inc. constants, variables, and maybe even xcom
> results (e.g., "return 123" or "return [t1, xcom_result]")
> 
> Some of the edge cases above could be solved by requiring that the return
> statement can only appear once at the bottom of the DAG function.
> I'd be happy to hear others' ideas on how to handle them.
> 
> 
> Shahar
> 
> 
> 
> On Thu, Jun 18, 2026, 15:27 Ash Berlin-Taylor <[email protected]> wrote:
> 
>> Somewhere I thought we had a list of BaseOperator init kwargs that cannot
>> be mapped — for instance you cannot map over the queue or pool slots
>> arguments today?
>> 
>> But the rest of what TP says I agree with. If you want the result of an
>> operator to be marked as the dag result, you can swap to use the @dag
>> decorator syntax:
>> 
>> @dag
>> def my_dag():
>>    task = EmptyOperator(taks_id=“xyz”)
>>    return task
>> 
>> I think that is sufficient?
>> 
>> -a
>> 
>>> On 17 Jun 2026, at 04:19, Tzu-ping Chung via dev <[email protected]>
>> wrote:
>>> 
>>> Using an operator argument such as
>>> 
>>>   task = EmptyOperator(task_id="xxx", result=True)
>>> 
>>> would hint that you might be able to do
>>> 
>>>   task = (
>>>       EmptyOperator
>>>       .partial(task_id="xxx")
>>>       .expand(result=[False, True])
>>>   )
>>> 
>>> which I don’t think we should allow. (No, I don’t think this makes sense
>> in the first place, but someone somewhere might.)
>>> 
>>> Of course, we could add checks so this emits an error when the dag is
>> parsed, but this is additional mental context (for both users and
>> maintainers) that could be entirely avoided in the first place. When AIP-52
>> proposed the setup/teardown syntax (which I want to be consistent with, as
>> mentioned previously) also did not propose MyOperator(..., setup=True).
>>> 
>>> Personally I don’t like
>>> 
>>>   @dag(return_task="my_task_id")
>>> 
>>> since it would be two places to edit if you want to change the task id
>> in the future. With the add_task() or return syntax mentioned previously,
>> the handle used is a Python variable, so an incorrect name would be easy to
>> catch with standard linters. The error emitted would also contain better
>> context (line numbers etc).
>>> 
>>> 
>>> TP
>>> 
>>> 
>>>> On 16 Jun 2026, at 21:17, Vincent Beck <[email protected]> wrote:
>>>> 
>>>> What about operators? Do we want to support only tasks using Taskflow
>> API? If not, a new parameter would work for both use cases (e.g.
>> `result=True`).
>>>> 
>>>> ```
>>>> @task(result=True)
>>>> def my_task(num):
>>>> return num*2
>>>> ```
>>>> 
>>>> ```
>>>> task = EmptyOperator(task_id="xxx", result=True)
>>>> ```
>>>> 
>>>> Or a new parameter in the Dag constructor:
>>>> 
>>>> ```
>>>> @dag(return_task="my_task_id")
>>>> ```
>>>> 
>>>> The inconvenience of the latter is you limit one task to be a Dag task
>> result (and it seems we want to enable having multiple task results per
>> Dag).
>>>> 
>>>> On 2026/06/16 10:20:44 Ash Berlin-Taylor wrote:
>>>>> TaskFlow automatically suffixes pretty close to this out of the box —
>> I think without the override we’d end up with my_task, my_task__1,
>> my_task__2, my_task__3 etc.
>> https://github.com/apache/airflow/blob/376cecdb9f258fdb6f81f264c48f281c1cd2aeb5/task-sdk/src/airflow/sdk/bases/decorator.py#L111-L150
>>>>> 
>>>>> -a
>>>>> 
>>>>>> On 16 Jun 2026, at 10:59, Tzu-ping Chung via dev <
>> [email protected]> wrote:
>>>>>> 
>>>>>> The loop would not work as-is (since it’d create multiple tasks with
>> the same id). But as currently designed, you CAN set multiple result tasks
>> on a dag. The result is always a dict keyed by tsk_id. So this slightly
>> modified example
>>>>>> 
>>>>>> @dag
>>>>>> def my_dag():
>>>>>> @task
>>>>>>  def t(x):
>>>>>>      return x
>>>>>> @result
>>>>>> @task
>>>>>>  def my_task(num):
>>>>>>      return num*2
>>>>>> for i in range(4):
>>>>>>     my_task.override(task_id=f"my_task_{i}")(t(i))
>>>>>> 
>>>>>> Would have the dag result
>>>>>> 
>>>>>> {
>>>>>>   "my_task_0": 0,
>>>>>>   "my_task_1": 2,
>>>>>>   "my_task_2": 4,
>>>>>>   "my_task_3": 6,
>>>>>> }
>>>>>> 
>>>>>> 
>>>>>>> On 16 Jun 2026, at 17:34, Ephraim Anierobi <
>> [email protected]> wrote:
>>>>>>> 
>>>>>>> Hi TP,
>>>>>>> 
>>>>>>> Thanks for bringing up this discussion.
>>>>>>> 
>>>>>>> I feel like `@result @task` is clean, however, it won't be clear
>> what the Dag's result is if the task is invoked multiple times in a dag.
>>>>>>> Take for example:
>>>>>>> 
>>>>>>> @dag
>>>>>>> def my_dag():
>>>>>>> @task
>>>>>>>  def t(x):
>>>>>>>      return x
>>>>>>> @result
>>>>>>> @task
>>>>>>>  def my_task(num):
>>>>>>>      return num*2
>>>>>>> for i in range(4):
>>>>>>>     my_task(t(i))
>>>>>>> 
>>>>>>> Unless I'm not understanding the @result well, but I feel like this
>> means, every invocation of `my_task` is a result of the dag.
>>>>>>> 
>>>>>>> If result is intended to be singular, I will prefer value inference
>> from the dag:
>>>>>>> 
>>>>>>> @dag
>>>>>>> def my_dag():
>>>>>>> @task
>>>>>>>     def my_task():
>>>>>>>         return 1
>>>>>>> return my_task()
>>>>>>> 
>>>>>>> AND
>>>>>>> 
>>>>>>> with DAG(...) as dag:
>>>>>>> output = f()
>>>>>>> dag.add_result(output)
>>>>>>> 
>>>>>>> Thanks
>>>>>>> - Ephraim
>>>>>>> 
>>>>>>> On Tue, 16 Jun 2026 at 08:37, Tzu-ping Chung via dev <
>> [email protected]> wrote:
>>>>>>> Hi all,
>>>>>>> 
>>>>>>> I’m currently working on the [Synchronous Dag Execution] feature and
>> trying to gather opinions on how the Taskflow API should work when we want
>> to mark a task as the dag’s “result task” (i.e. “the return value is a
>> final output of the dag, not an intermediate value”).
>>>>>>> 
>>>>>>> [Synchronous Dag Execution]:
>> https://github.com/apache/airflow/issues/51711
>>>>>>> 
>>>>>>> ## Prior art (kind of)
>>>>>>> 
>>>>>>> We currently have the setup/teardown Taskflow API like this:
>>>>>>> 
>>>>>>> @setup
>>>>>>> def f1(): ...
>>>>>>> 
>>>>>>> @task
>>>>>>> def f2(): ...
>>>>>>> 
>>>>>>> setup1 = f1()  # This is a setup task.
>>>>>>> 
>>>>>>> t2 = f2()  # This is a normal task.
>>>>>>> setup2 = t2.as_setup()  # This is a setup task.
>>>>>>> 
>>>>>>> A teardown variant also exists for both cases.
>>>>>>> 
>>>>>>> ## The decorator syntax
>>>>>>> 
>>>>>>> The most straightforward syntax would be to have a @result decorator
>> on a plain Python function. However, I don’t like this since a result task
>> still has all the same arguments as a non-result task. Setup and teardown
>> tasks don’t accept most task arguments. If @result needs to work on a plain
>> function, it would need to duplicate and forward all the arguments on
>> @task. I feel we can avoid this redundancy by requiring @result to be used
>> ON TOP OF @task instead:
>>>>>>> 
>>>>>>> @result
>>>>>>> @task(put your arguments here...)
>>>>>>> def f(): ...
>>>>>>> 
>>>>>>> We COULD also make using @result without @task a shorthand to
>> argument-less calls (which is probably common?)
>>>>>>> 
>>>>>>> # This...
>>>>>>> @result
>>>>>>> def f(): ...
>>>>>>> 
>>>>>>> # Is equivalent to...
>>>>>>> @result
>>>>>>> @task
>>>>>>> def f(): ...
>>>>>>> 
>>>>>>> Alternatively, we could use a fluent interface:
>>>>>>> 
>>>>>>> @task(arguments here...).result
>>>>>>> def f(): ...
>>>>>>> 
>>>>>>> Pro: avoids needing a top-level name. Con: Not a common pattern in
>> Airflow.
>>>>>>> 
>>>>>>> ## The method syntax
>>>>>>> 
>>>>>>> I don’t think adding a method similar to as_setup/teardown makes
>> sense here. It makes sense for setup/teardown because it allows the same
>> body of code to be BOTH a setup/teardown task AND a normal task at the same
>> time, as shown above. This does not make sense for a result task—a task
>> either returns the result, or it doesn’t. If we want a method-based syntax,
>> it makes more sense to have a method on the dag:
>>>>>>> 
>>>>>>> with DAG(...) as dag:
>>>>>>>     @task
>>>>>>>     def f():
>>>>>>> 
>>>>>>>     t = f()
>>>>>>>     dag.add_result(t)
>>>>>>> 
>>>>>>> ## For @dag decorator
>>>>>>> 
>>>>>>> One more syntax that only makes sense here is we can automatically
>> detect the return value of an @dag-decorated function:
>>>>>>> 
>>>>>>> @dag
>>>>>>> def my_dag():
>>>>>>>     @task
>>>>>>>     def f1(): ...
>>>>>>> 
>>>>>>>     @task
>>>>>>>     def f2(v): ...
>>>>>>> 
>>>>>>>     result = f2(f1())
>>>>>>> 
>>>>>>>     return result  # Marks f2 as the result task!
>>>>>>> 
>>>>>>> ---------------
>>>>>>> 
>>>>>>> Looking forward to hearing thoughts on the above, and more ideas on
>> possible syntaxes.
>>>>>>> 
>>>>>>> TP
>>>>>>> 
>>>>>>> 
>>>>>>> 
>>>>>>> 
>>>>>>> ---------------------------------------------------------------------
>>>>>>> To unsubscribe, e-mail: [email protected]
>>>>>>> For additional commands, e-mail: [email protected]
>>>>>>> 
>>>>>> 
>>>>>> 
>>>>>> ---------------------------------------------------------------------
>>>>>> To unsubscribe, e-mail: [email protected]
>>>>>> For additional commands, e-mail: [email protected]
>>>>>> 
>>>>> 
>>>>> 
>>>> 
>>>> ---------------------------------------------------------------------
>>>> To unsubscribe, e-mail: [email protected]
>>>> For additional commands, e-mail: [email protected]
>>>> 
>>> 
>>> 
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: [email protected]
>>> For additional commands, e-mail: [email protected]
>>> 
>> 
>> 
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: [email protected]
>> For additional commands, e-mail: [email protected]
>> 
>> 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to