diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8b2d08b1ea3e5..00bd71f9ab819 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -759,6 +759,14 @@ repos:
files: ^\.pre-commit-config\.yaml$|^docs/spelling_wordlist\.txt$
require_serial: true
pass_filenames: false
+ - id: update-openapi-spec-tags-to-be-sorted
+ name: Sort alphabetically openapi spec tags
+ entry: ./scripts/ci/pre_commit/sort_tags_in_openapi_spec.py
+ types: [yaml]
+ language: python
+ files: ^\.pre-commit-config\.yaml$|^airflow/api_connexion/openapi/v1\.yaml$
+ require_serial: true
+ pass_filenames: false
- id: lint-helm-chart
name: Lint Helm Chart
entry: ./scripts/ci/pre_commit/helm_lint.py
diff --git a/contributing-docs/08_static_code_checks.rst b/contributing-docs/08_static_code_checks.rst
index 0469ee31ead3f..428f0ec5cf589 100644
--- a/contributing-docs/08_static_code_checks.rst
+++ b/contributing-docs/08_static_code_checks.rst
@@ -359,6 +359,8 @@ require Breeze Docker image to be built locally.
+-----------------------------------------------------------+--------------------------------------------------------------+---------+
| update-migration-references | Update migration ref doc | * |
+-----------------------------------------------------------+--------------------------------------------------------------+---------+
+| update-openapi-spec-tags-to-be-sorted | Sort alphabetically openapi spec tags | |
++-----------------------------------------------------------+--------------------------------------------------------------+---------+
| update-providers-dependencies | Update dependencies for provider packages | |
+-----------------------------------------------------------+--------------------------------------------------------------+---------+
| update-reproducible-source-date-epoch | Update Source Date Epoch for reproducible builds | |
diff --git a/dev/breeze/doc/images/output_static-checks.svg b/dev/breeze/doc/images/output_static-checks.svg
index 2b5f586c73cac..9bd79196b4bca 100644
--- a/dev/breeze/doc/images/output_static-checks.svg
+++ b/dev/breeze/doc/images/output_static-checks.svg
@@ -359,10 +359,10 @@
│update-extras | update-in-the-wild-to-be-sorted | │
│update-inlined-dockerfile-scripts | update-installed-providers-to-be-sorted | │
│update-installers | update-local-yml-file | update-migration-references | │
-│update-providers-dependencies | update-reproducible-source-date-epoch | │
-│update-spelling-wordlist-to-be-sorted | update-supported-versions | │
-│update-vendored-in-k8s-json-schema | update-version | validate-operators-init | │
-│yamllint) │
+│update-openapi-spec-tags-to-be-sorted | update-providers-dependencies | │
+│update-reproducible-source-date-epoch | update-spelling-wordlist-to-be-sorted | │
+│update-supported-versions | update-vendored-in-k8s-json-schema | update-version |│
+│validate-operators-init | yamllint) │
│--show-diff-on-failure-sShow diff for files modified by the checks.│
│--initialize-environmentInitialize environment before running checks.│
│--max-initialization-attemptsMaximum number of attempts to initialize environment before giving up.│
diff --git a/dev/breeze/doc/images/output_static-checks.txt b/dev/breeze/doc/images/output_static-checks.txt
index 37736eb0039b0..f9883263fbe18 100644
--- a/dev/breeze/doc/images/output_static-checks.txt
+++ b/dev/breeze/doc/images/output_static-checks.txt
@@ -1 +1 @@
-74e4402e7abd79d80bca112783891cb1
+cbf4a8cef8333b856b04552d5d771f7b
diff --git a/dev/breeze/src/airflow_breeze/pre_commit_ids.py b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
index caa51ca62c5d2..a74886ea197bb 100644
--- a/dev/breeze/src/airflow_breeze/pre_commit_ids.py
+++ b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
@@ -134,6 +134,7 @@
"update-installers",
"update-local-yml-file",
"update-migration-references",
+ "update-openapi-spec-tags-to-be-sorted",
"update-providers-dependencies",
"update-reproducible-source-date-epoch",
"update-spelling-wordlist-to-be-sorted",
diff --git a/scripts/ci/pre_commit/sort_tags_in_openapi_spec.py b/scripts/ci/pre_commit/sort_tags_in_openapi_spec.py
new file mode 100755
index 0000000000000..0961dffce8f41
--- /dev/null
+++ b/scripts/ci/pre_commit/sort_tags_in_openapi_spec.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import re
+from pathlib import Path
+
+if __name__ not in ("__main__", "__mp_main__"):
+ raise SystemExit(
+ "This file is intended to be executed as an executable program. You cannot use it as a module."
+ f"To run this script, run the ./{__file__} command"
+ )
+
+AIRFLOW_SOURCES = Path(__file__).parents[3].resolve()
+
+
+def sort_tags(filename: Path):
+ with filename.open() as f:
+ lines = f.readlines()
+
+ # Find the start index of the tags list
+ tag_start_pattern = re.compile(r"tags:")
+ start_index = next(
+ (index + 1 for index, line in enumerate(lines) if tag_start_pattern.match(line)),
+ None,
+ )
+
+ if start_index is None:
+ print("Tags list not found in the YAML file.")
+ return
+
+ # Find the end index of the tags list
+ end_index = start_index
+ tag_pattern = re.compile(r"\s*-\s*name:")
+ while end_index < len(lines) and tag_pattern.match(lines[end_index]):
+ end_index += 1
+
+ # Extract and sort the tags
+ tags_lines = lines[start_index:end_index]
+ sorted_tags_lines = sorted(tags_lines, key=lambda x: x.strip().split(": ")[-1])
+
+ # Replace the sorted tags in the original content
+ lines[start_index:end_index] = sorted_tags_lines
+
+ with filename.open("w") as f:
+ f.write("".join(lines))
+
+
+if __name__ == "__main__":
+ openapi_spec_filepath = Path(AIRFLOW_SOURCES) / "airflow" / "api_connexion" / "openapi" / "v1.yaml"
+ sort_tags(openapi_spec_filepath)