AIP-97 (draft): failure context propagation and transparent infra retry#66405
Draft
1fanwang wants to merge 3 commits into
Draft
AIP-97 (draft): failure context propagation and transparent infra retry#664051fanwang wants to merge 3 commits into
1fanwang wants to merge 3 commits into
Conversation
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
from
May 5, 2026 10:21
20d6bd9 to
8c53810
Compare
This was referenced May 5, 2026
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
from
July 16, 2026 05:33
57f338e to
fcc393a
Compare
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
from
July 24, 2026 19:28
fcc393a to
bd8e7ec
Compare
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
from
July 24, 2026 19:38
bd8e7ec to
e87f0ba
Compare
…column on_task_instance_failed gains an optional failure_kind argument, a TaskFailureKind str enum (infra / application / timeout / manual) in airflow_shared.state. pluggy dispatches by parameter name, so a listener that does not declare it keeps working. The worker populates application or timeout; a manual mark-failed populates manual. The executor's reason token persists on the task instance as a new nullable infra_reason column. Foundation for AIP-97; the Kubernetes classifier (infra) and the opt-in retry refund follow as separate PRs. Signed-off-by: 1fanwang <1fannnw@gmail.com>
…he scheduler The Kubernetes executor's classify_pod_failure() maps a node-level disruption (Evicted, Preempting, DisruptionTarget, node shutdown) or a pod removed while its task is running (PodDeleted — drain, preemption, spot reclaim, force-delete) to failure_kind=infra; a container that ended on its own (an app crash, an OOM against its own limit) to application. The classification rides a side channel on BaseExecutor that the scheduler reads once and passes to the listener with the executor's reason token. An unclassified death stays None, so the scheduler stays conservative and never guesses infra. Builds on the failure_kind foundation; the opt-in retry refund follows. Signed-off-by: 1fanwang <1fannnw@gmail.com>
…-in) When failure_kind is infra, [core] infra_failure_refund_retries bumps max_tries so the attempt is given back instead of drawing on the user's retries, bounded by [core] max_infra_refunds. Off by default; application, timeout, and manual failures never refund. This extends the Kubernetes executor's existing behavior — a pod that dies before the task starts is already requeued without consuming a retry — to the mid-execution case, now that the failure is classified. Completes the AIP-97 foundation stack. Signed-off-by: 1fanwang <1fannnw@gmail.com>
1fanwang
force-pushed
the
1fanwang/aip97-failure-details
branch
from
July 24, 2026 22:39
e87f0ba to
7d568c9
Compare
This was referenced Jul 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Concrete draft backing AIP-97 (in DRAFT on the cwiki), opened so the listener-side API shape can be discussed against working code. Marked draft for that reason. This is the consolidated view; the Merge plan below decomposes it into three reviewable PRs.
Why
on_task_instance_failedsees only the worker-sideerror. When a task is killed from outside the worker — a pod eviction, an OOM kill, a force-deleted pod, a lost heartbeat — the worker never raises, so the listener gets nothing that says why. A pod evicted mid-run and a realValueErrorlook identical to alerting and lineage. The same blind spot burns retries:is_eligible_to_retry()istry_number <= max_trieswithmax_tries = task.retries, so an eviction spends a retry the user meant for their own code.What this adds
Pillar 1 — failure context.
on_task_instance_failedgains an optionalfailure_kindargument, aTaskFailureKindstring enum (infra/application/timeout/manual). pluggy dispatches by parameter name, so a listener that does not declare it keeps working. The executor's reason token (Evicted,PodDeleted, ...) persists on the task instance as a new nullableinfra_reasoncolumn. The Kubernetes executor'sclassify_pod_failure()maps a node-level disruption (Evicted,Preempting,DisruptionTarget, node shutdown) or a pod removed while running (PodDeleted— drain, preemption, spot reclaim, force-delete) toinfra; a container that ended on its own (an app crash, an OOM against its own limit) toapplication. An unclassified death staysNone.Pillar 2 — transparent infra retry (opt-in). When
failure_kind == infra,[core] infra_failure_refund_retriesrefunds the attempt (max_tries += 1) instead of drawing on the user'sretries, bounded bymax_infra_refunds. Off by default; application, timeout, and manual failures never refund. This extends the executor's existing pre-execution behavior — a pod that dies before the task starts is already requeued without consuming a retry — to the mid-run case.Merge plan
Lands as three incremental PRs, each reviewable on its own:
failure_kindon the listener +infra_reasoncolumn (the type/hookspec foundation).classify_pod_failure()surfacesinfrato the scheduler.max_tries, gated and capped).Testing
Full-stack on a real KubernetesExecutor cluster (kind): a running task pod force-deleted,
classify_pod_failurereadsPodDeleted, the scheduler refunds while the TI is stillRUNNING:The non-infra paths spend the retry, verified live on LocalExecutor + Postgres: a task mark-failed is
manual, a DAG-run mark-failed sets the terminal state first so it is never misclassified, anexecution_timeoutistimeout, adagrun_timeoutskips the task. None refunds. 34 unit tests plus scheduler-wiring, ruff clean.Open questions (for the AIP discussion)
max_infra_refundscap here.max_tries += 1(one write, no migration, but shares the counter with application retries) versus a dedicatedinfra_retry_countcolumn.Was generative AI tooling used to co-author this PR?
Generated-by: GitHub Copilot CLI following the guidelines