Repository navigation
Description
Under which category would you file this issue?
Providers
Apache Airflow version
3.3.1
What happened and how to reproduce it?
Executor KubernetesExecutor(parallelism=60) reported that the task instance <TaskInstance: xxxx scheduled__2026-09-16T07:00:00+00:00 map_index=5 [queued] ti_id=01a0a95a-37ed-7177-b212-4e25272f5c2a> finished with state failed, but the task instance's state attribute is queued. Learn more: https://airflow.apache.org/docs/apache-airflow/stable/troubleshooting.html#task-state-changed-externally Extra info: Pod failed because of None [airflow.task] loc=taskinstance.py:1841"
On the screenshot below it says that first attempt is not visible, and it succeeded performing only 2nd attempt.
What you think should happen instead?
It would be valuable to support different retry policies based on the cause of a failure, particularly for application failures versus infrastructure or connectivity failures.
Task authors may want deterministic application errors to fail immediately, since rerunning the same code with the same input is unlikely to succeed. They may also need to disable retries when an operation is non-idempotent or otherwise unsafe to repeat.
However, disabling retries entirely is risky. A task can fail before the operation even begins—for example, because its worker pod cannot start—or because of a transient network problem or temporary third-party service outage. These failures may be safe and worthwhile to retry.
Conversely, applying one retry policy to every failure can cause deterministic application errors to run repeatedly when they should fail immediately.
Ideally, Airflow would allow different behavior for at least the following categories:
- Application failures that should fail immediately
- Infrastructure or pre-execution failures that should be retried without consuming the application retry budget
- Transient connectivity or third-party failures that should follow a configurable retry policy
I am not sure what the ideal implementation would look like, but representing application errors, infrastructure failures, and third-party timeouts with the same generic failed state makes it difficult to choose a safe and effective retry strategy.
Apologies for covering several concerns in one ticket, but I believe they are closely related: without knowing why a task failed, Airflow cannot determine—or allow users to configure—the appropriate retry behavior.
Operating System
Debian GNU/Linux 12 (bookworm)
Deployment
Official Apache Airflow Helm Chart
Apache Airflow Provider(s)
cncf-kubernetes
Versions of Apache Airflow Providers
apache-airflow-providers-amazon==9.34.0
apache-airflow-providers-cncf-kubernetes==10.21.0
apache-airflow-providers-common-compat==1.18.0
apache-airflow-providers-common-io==1.8.0
apache-airflow-providers-common-sql==2.1.0
apache-airflow-providers-databricks==7.18.1
apache-airflow-providers-fab==3.8.0
apache-airflow-providers-github==2.11.3
apache-airflow-providers-hashicorp==4.8.0
apache-airflow-providers-http==6.0.5
apache-airflow-providers-microsoft-mssql==4.7.0
apache-airflow-providers-mysql==6.6.1
apache-airflow-providers-postgres==7.0.1
apache-airflow-providers-sftp==6.0.1
apache-airflow-providers-slack==9.10.2
apache-airflow-providers-smtp==3.0.3
apache-airflow-providers-ssh==6.0.1
apache-airflow-providers-standard==1.17.0
Official Helm Chart version
1.22.0 (latest released)
Kubernetes Version
1.34.9
Helm Chart configuration
No response
Docker Image customizations
No response
Anything else?
No response
Are you willing to submit PR?
- Yes I am willing to submit a PR!
Code of Conduct
- I agree to follow this project's Code of Conduct
Activity
kartik00052 commentedon Sep 22, 2026
Reading through the retry/failure paths after seeing this issue and #73395 — a couple of things worth pointing at directly with code references, plus a possible way to close the remaining gap.
A bounded, separately-counted infra retry already exists for one case.
KubernetesExecutor._is_pre_execution_failure()(inkubernetes_executor.py) already distinguishes a pod that failed before the task instance reachedRUNNINGfrom one that failed after — it checksti_state == QUEUEDat failure time, and if that holds, the pod is requeued on its own counter (pod_launch_failure_max_retries) without ever touchingtask.retries. That's exactly the "infra failure shouldn't burn the application's retry budget" behavior this issue is asking for — it just only covers pod-launch failures.The mid-execution case — the one in this issue's screenshot — doesn't get that treatment.
Once a task instance is
RUNNINGand gets killed externally (pod eviction, node drain, SIGTERM), that's the case #73395 is fixing: right now onmain,_on_termin the task runner doesn't raise anything, soexecute()silently resumes and the task can end up reportedSUCCESSeven though the work was interrupted. #73395 fixes that by raisingAirflowTaskTerminated.But looking at how
task_runner.pyhandles that exception (the except-chain aroundexcept AirflowTaskTerminated), it goes straight tostate = TaskInstanceState.FAILED— it never calls_handle_current_task_failed, which is the path that checksTaskInstance.is_eligible_to_retry(). So after #73395 merges, a mid-execution infra kill won't silently succeed anymore, but it will permanently fail with zero retries even if the task hasretries=5set. #73395's own PR description flags this directly: "Whether an infrastructure kill should instead be retry-eligible is a real question — it is what #73238 is about... changing that classification also changes the UI 'mark failed' path, so it is left alone here."So the gap this issue is really about, narrowed down:
AirflowTaskTerminatedneeds the same kind of separate, bounded retry budget that pod-launch failures already get — not the application'stask.retries, and not zero.A possible shape for that, modeled on the existing pattern rather than inventing something new:
infrastructure_failure_max_retries, parallel topod_launch_failure_max_retries.pod_launch_failure_max_retriesis tracked inKubernetesExecutor.pod_launch_attempts, an in-memory counter on the scheduler process.AirflowTaskTerminatedis raised inside the task runner process itself — it doesn't have access to that. So this can't just reuse the same storage; the counter would need to live somewhere both sides can see it (task instance / task instance history row seems like the natural place, but I haven't traced whether there's already a suitable field, or whether this needs a new column/attempt-tracking concept).AirflowTaskTerminatedunconditionally terminal, it would need to go through something like_handle_current_task_failed's retry-eligibility check, but against the new infra-budget instead of (or in addition to)task.retries.Given this touches task-sdk (where
AirflowTaskTerminatedis raised) and core retry semantics (not just the K8s executor), I'd guess this needs at least a short AIP rather than going straight to a PR, but I'm not certain where the line is for a change this size — happy to be corrected.Open questions I don't have good answers to yet:
AirflowTaskTerminateditself is raised in executor-agnostic code?Happy to take a pass at drafting this out further (or an AIP if that's the right next step) if this direction seems reasonable — wanted to sanity-check the approach here first rather than show up with a PR that reopens a decision #73395 intentionally deferred.
namanjain24-sudo commentedon Sep 22, 2026
Thanks for the detailed breakdown, @kartik00052 — matches what I ran into while working on #73395. Once that lands, I'd like to pick up the infra-retry-budget piece you described (a separate, bounded retry for
AirflowTaskTerminated, not the application'stask.retries).Since it touches task-sdk and core retry semantics across processes, would a short AIP be the right next step here, or is a design write-up in the PR description enough for something this size? Wanted to check before writing code.