tritsystem opened a new issue, #72544:
URL: https://github.com/apache/airflow/issues/72544

   ### Under which category would you file this issue?
   
   Task SDK
   
   ### Apache Airflow version
   
   apache-airflow-core 3.3.1 / apache-airflow-task-sdk 1.3.1 (also confirmed 
present on `main` @ 6d4808b4, 2026-09-05)
   
   ### What happened and how to reproduce it?
   
   **Issue Description**
   
   `executor_config` inherited from a shared `default_args` dict is ALIASED 
(not copied) across every task that doesn't override it — and across every DAG 
built from that same `default_args` dict. Reusing one `default_args` module 
across multiple DAG files is a common, docs-blessed pattern; mutating 
`task_instance.executor_config` at runtime (e.g. to set a Kubernetes pod 
override) is also a pattern the Kubernetes-executor docs recommend. Combining 
the two silently corrupts sibling tasks and unrelated DAGs.
   
   **Steps to reproduce:**
   
   ```python
   import copy
   from airflow.sdk import DAG
   from airflow.providers.standard.operators.python import PythonOperator
   
   default_args = {
       "executor_config": {
           "KubernetesExecutor": {"image": "base:latest"},
       },
   }
   
   with DAG(dag_id="dag1", default_args=default_args, schedule=None) as dag1:
       task_a = PythonOperator(task_id="task_a", python_callable=lambda: None)
       task_b = PythonOperator(task_id="task_b", python_callable=lambda: None)
   
   print(task_a.executor_config is task_b.executor_config)  # True -- should be 
False
   
   # Mutate only task_a's config (a documented pattern for per-task pod 
overrides)
   task_a.executor_config["KubernetesExecutor"]["image"] = "custom:v2"
   task_a.executor_config["KubernetesExecutor"]["new_key"] = "leaked"
   
   print(task_b.executor_config)
   # task_b's config is silently corrupted too -- it never should have changed
   
   # Same aliasing reaches across entirely unrelated DAGs built from the same 
default_args:
   with DAG(dag_id="dag2", default_args=default_args, schedule=None) as dag2:
       task_c = PythonOperator(task_id="task_c", python_callable=lambda: None)
   
   print(task_c.executor_config)  # also corrupted -- dag2 never touched dag1's 
task
   ```
   
   Output: `task_a.executor_config is task_b.executor_config` prints `True`, 
and after mutating only `task_a`'s config, both `task_b` and `task_c` (in an 
entirely different DAG) show the same unwanted mutation.
   
   ### What you think should happen instead?
   
   Each task should get an independent copy of `executor_config` inherited from 
`default_args`, the same way callback fields (`on_failure_callback` etc.) 
already are.
   
   **Root cause:**
   - `DAG.default_args` uses `converter=copy.copy` (shallow) — 
`task-sdk/src/airflow/sdk/definitions/dag.py`.
   - `BaseOperatorMeta.get_merged_defaults` shallow-copies again: `dag_args = 
copy.copy(dag.default_args)` — `task-sdk/src/airflow/sdk/bases/operator.py` 
(~line 140).
   - `BaseOperator.__init__` then does `self.executor_config = executor_config 
or {}` (operator.py, line ~1151) with **no per-task copy at all** — unlike 
callback fields, which ARE safely normalized via `_collect_from_input()`'s 
`list(...)` rebuild (operator.py, line ~457).
   - The ORM `TaskInstance` aliases the same dict again: `self.executor_config 
= task.executor_config` (`airflow-core/src/airflow/models/taskinstance.py`, 
line ~1019) — extending the exposure window.
   
   Every "copy" in the chain is shallow, so the dict object under 
`default_args["executor_config"]` ends up literally shared by reference across 
every task/DAG that inherits it without overriding it.
   
   **Suggested fix:** apply `copy.deepcopy` (or a per-field normalizer 
analogous to `_collect_from_input`) to dict-valued args like `executor_config` 
(and similarly-shaped fields, e.g. `params`) when merging `default_args` into 
each task, matching the treatment callback fields already get.
   
   Searched issues/PRs for this exact mechanism (`executor_config default_args 
shared`, `default_args mutable executor_config`, `executor_config shared 
reference`, etc.) — no prior report found; the closest hits (#47702, #53459, 
#26568) address key-validation and clear-behavior, not reference sharing.
   
   ### Operating System
   
   Windows 11
   
   ### Deployment
   
   Virtualenv installation
   
   ### Anything else?
   
   Found via a systematic property-based audit (copy/clone independence across 
constructor default arguments) run across several widely-used Python libraries. 
The repro above was written and independently run twice, with identical results 
both times. Happy to send a PR if a maintainer confirms the fix direction 
(deepcopy vs. a targeted normalizer).
   
   ### Are you willing to submit PR?
   
   - [ ] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's Code of Conduct
   


-- 
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