This is an automated email from the ASF dual-hosted git repository.
vatsrahul1001 pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-4-test by this push:
new 79e1aea617c [v3-4-test] Reject non-positive max_active_runs when
creating a backfill (#75071) (#75320)
79e1aea617c is described below
commit 79e1aea617c6ea237a115f7aa9666fb9fdab29de
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sat Oct 24 14:36:50 2026 +0000
[v3-4-test] Reject non-positive max_active_runs when creating a backfill
(#75071) (#75320)
POST /api/v2/backfills accepted max_active_runs of 0 or below and
created the backfill. The scheduler only starts a backfill run while
the number of running runs is below max_active_runs, so with 0 or a
negative value every run stayed queued forever. The dry-run endpoint
shares the same body and accepted it as well.
The legacy `airflow backfill create --max-active-runs` already rejects
these values with positive_int(allow_zero=False), and the UI backfill
form sets min=1. BackfillPostBody now requires max_active_runs >= 1, so
the API returns 422 instead.
(cherry picked from commit 7d94a66507ed1085f2e9773453e6fd355aa0bd77)
Co-authored-by: W. H. Wang <[email protected]>
---
.../api_fastapi/core_api/datamodels/backfills.py | 2 +-
.../core_api/openapi/v2-rest-api-generated.yaml | 1 +
.../airflow/ui/openapi-gen/requests/schemas.gen.ts | 1 +
.../core_api/routes/public/test_backfills.py | 28 ++++++++++++++++++++++
.../src/airflowctl/api/datamodels/generated.py | 2 +-
5 files changed, 32 insertions(+), 2 deletions(-)
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/backfills.py
b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/backfills.py
index 3833825479d..808815da72a 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/backfills.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/backfills.py
@@ -37,7 +37,7 @@ class BackfillPostBody(StrictBaseModel):
run_backwards: bool = False
dag_run_conf: dict | None = None
reprocess_behavior: ReprocessBehavior = ReprocessBehavior.NONE
- max_active_runs: int = 10
+ max_active_runs: int = Field(default=10, ge=1)
run_on_latest_version: bool | None = Field(
default=None,
description="Run on the latest bundle version of the Dag for each
backfilled run. "
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
index 057fdbc91b4..aecd0bdeaf0 100644
---
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
+++
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
@@ -13082,6 +13082,7 @@ components:
default: none
max_active_runs:
type: integer
+ minimum: 1.0
title: Max Active Runs
default: 10
run_on_latest_version:
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
index d95a83b65db..ccbdf81c024 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
@@ -985,6 +985,7 @@ export const $BackfillPostBody = {
},
max_active_runs: {
type: 'integer',
+ minimum: 1,
title: 'Max Active Runs',
default: 10
},
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_backfills.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_backfills.py
index 5c852117c1d..926d5271fbe 100644
---
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_backfills.py
+++
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_backfills.py
@@ -576,6 +576,20 @@ class TestCreateBackfill(TestBackfillEndpoint):
assert response.status_code == 404
assert response.json().get("detail") == "Could not find dag
DAG_NOT_EXIST"
+ @pytest.mark.parametrize("max_active_runs", [0, -3])
+ def test_create_backfill_rejects_non_positive_max_active_runs(self,
max_active_runs, test_client):
+ response = test_client.post(
+ url="/backfills",
+ json={
+ "dag_id": "TEST_DAG_1",
+ "from_date": "2024-01-01T00:00:00Z",
+ "to_date": "2024-01-02T00:00:00Z",
+ "max_active_runs": max_active_runs,
+ },
+ )
+ assert response.status_code == 422
+ assert response.json()["detail"][0]["loc"] == ["body",
"max_active_runs"]
+
def test_no_schedule_dag(self, session, dag_maker, test_client):
with dag_maker(session=session, dag_id="TEST_DAG_1", schedule=None) as
dag:
EmptyOperator(task_id="mytask")
@@ -1330,6 +1344,20 @@ class TestCreateBackfillDryRun(TestBackfillEndpoint):
assert response.status_code == 422
assert "Invalid input for param validated_number" in
response.json().get("detail")
+ @pytest.mark.parametrize("max_active_runs", [0, -3])
+ def
test_create_backfill_dry_run_rejects_non_positive_max_active_runs(self,
max_active_runs, test_client):
+ response = test_client.post(
+ url="/backfills/dry_run",
+ json={
+ "dag_id": "TEST_DAG_1",
+ "from_date": "2024-01-01T00:00:00Z",
+ "to_date": "2024-01-02T00:00:00Z",
+ "max_active_runs": max_active_runs,
+ },
+ )
+ assert response.status_code == 422
+ assert response.json()["detail"][0]["loc"] == ["body",
"max_active_runs"]
+
@pytest.mark.parametrize(
("repro_act", "repro_exp", "run_backwards", "status_code"),
[
diff --git a/airflow-ctl/src/airflowctl/api/datamodels/generated.py
b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
index 7ed99bea665..a705d2eaf66 100644
--- a/airflow-ctl/src/airflowctl/api/datamodels/generated.py
+++ b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
@@ -1842,7 +1842,7 @@ class BackfillPostBody(BaseModel):
run_backwards: Annotated[bool | None, Field(title="Run Backwards")] = False
dag_run_conf: Annotated[dict[str, Any] | None, Field(title="Dag Run
Conf")] = None
reprocess_behavior: ReprocessBehavior | None = "none"
- max_active_runs: Annotated[int | None, Field(title="Max Active Runs")] = 10
+ max_active_runs: Annotated[int | None, Field(ge=1, title="Max Active
Runs")] = 10
run_on_latest_version: Annotated[
bool | None,
Field(