{"owner":"apache","repo":"airflow","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"files":{"AGENTS.md":" <!-- SPDX-License-Identifier: Apache-2.0\n      https://www.apache.org/licenses/LICENSE-2.0 -->\n\n# AGENTS instructions\n\n## Naming\n\nWrite **Dag** (title case) in all prose. Keep the all-caps or lowercase\nspelling only when reproducing a literal code token — never rewrite these,\neven inside fenced code blocks:\n\n- Python: the SDK class `DAG` (`from airflow.sdk import DAG`,\n  `dag = DAG(\"my_dag\", ...)`); identifiers like `dag_id`, `dag`, `my_dag`.\n- CLI: `airflow dags list`, `airflow dags test`, etc.\n- Paths and config keys: `dag_processing/`, `dagprocessor`, `get_dag`, etc.\n- Anti-pattern quotes that show the wrong form to teach the rule itself\n  (e.g., `Use \"DAG\" — always write \"Dag\"`).\n\nDon't spell out **Directed Acyclic Graph** except for historical context.\n\n## Environment Setup\n\n- Install prek: `uv tool install prek`\n- Enable commit hooks: `prek install`\n- Install breeze shim (one-time, per machine): `scripts/tools/setup_breeze` — installs `~/.local/bin/breeze` that runs breeze via `uvx` from the current git worktree's `dev/breeze` (so each worktree, including ephemeral agent worktrees, gets its own breeze tied to its sources). See [ADR 0017](dev/breeze/doc/adr/0017-use-uvx-to-run-breeze-from-local-sources.md).\n- **Never run pytest, python, or airflow commands directly on the host** — always use `breeze`.\n- Place temporary scripts in `dev/` (mounted as `/opt/airflow/dev/` inside Breeze).\n\n## Commands\n\n`<PROJECT>` is folder where pyproject.toml of the package you want to test is located. For example, `airflow-core` or `providers/amazon`.\n`<target_branch>` is the branch the PR will be merged into — usually `main`, but could be `v3-1-test` when creating a PR for the 3.1 branch.\n\n<!-- START generated-commands, please keep comment here to allow auto update -->\n- **Run a single test:** `uv run --project <PROJECT> pytest path/to/test.py::TestClass::test_method -xvs`\n- **Run a test file:** `uv run --project <PROJECT> pytest path/to/test.py -xvs`\n- **Run all tests in package:** `uv run --project <PROJECT> pytest path/to/package -xvs`\n- **If uv tests fail with missing system dependencies, run the tests with breeze**: `breeze run pytest <tests> -xvs`\n- **Run a Python script:** `uv run --project <PROJECT> python dev/my_script.py`\n- **Run core or provider tests suite in parallel:** `breeze testing <test_group> --run-in-parallel` (test groups: `core-tests`, `providers-tests`)\n- **Run core or provider db tests suite in parallel:** `breeze testing <test_group> --run-db-tests-only --run-in-parallel` (test groups: `core-tests`, `providers-tests`)\n- **Run core or provider non-db tests suite in parallel:** `breeze testing <test_group> --skip-db-tests --use-xdist` (test groups: `core-tests`, `providers-tests`)\n- **Run single provider complete test suite:** `breeze testing providers-tests --test-type \"Providers[PROVIDERS_LIST]\"` (e.g., `Providers[google]` or `Providers[amazon]` or \"Providers[amazon,google]\")\n- **Run Helm tests in parallel with xdist** `breeze testing helm-tests --use-xdist`\n- **Run Helm tests with specific K8s version:** `breeze testing helm-tests --use-xdist --kubernetes-version 1.35.0`\n- **Run specific Helm test type:** `breeze testing helm-tests --use-xdist --test-type <type>` (types: `airflow_aux`, `airflow_core`, `apiserver`, `dagprocessor`, `other`, `redis`, `security`, `statsd`, `webserver`)\n- **Run other suites of tests** `breeze testing <test_group>` (test groups: `airflow-ctl-tests`, `docker-compose-tests`, `task-sdk-tests`)\n- **Run scripts tests:** `uv run --project scripts pytest scripts/tests/ -xvs`\n- **Run Airflow CLI:** `breeze run airflow dags list`\n- **Type-check (non-providers):** run the prek hook — `prek run mypy-<project> --all-files` (e.g. `mypy-airflow-core`, `mypy-task-sdk`, `mypy-shared-logging`; each `shared/<dist>` workspace member has its own `mypy-shared-<dist>` hook). The hook uses a dedicated virtualenv and mypy cache under `.build/mypy-venvs/<hook>/` and `.build/mypy-caches/<hook>/`; mypy itself is installed from `uv.lock` via the `mypy` dependency group (`uv sync --group mypy`), so it never mutates your project `.venv`. The hook prefers `uv` from the project's main `.venv/bin/uv` (installed by `uv sync` — `uv` is part of the `dev` dependency group via the `all` extras) for a project-pinned uv version; it falls back to `uv` on `$PATH` with a warning if that binary is missing. Clear with `breeze down --cleanup-mypy-cache`.\n- **Type-check (providers):** `breeze run mypy path/to/code`\n- **Lint with ruff only:** `prek run ruff --from-ref <target_branch>`\n- **Format with ruff only:** `prek run ruff-format --from-ref <target_branch>`\n- **Run regular (fast) static checks:** `prek run --from-ref <target_branch> --stage pre-commit`\n- **Run manual (slower) checks:** `prek run --from-ref <target_branch> --stage manual --skip compile-ui-assets-dev --skip view-skill-eval --skip run-skill-eval-codex` (the skipped hooks start long-running local servers or provision the opt-in Codex environment rather than run checks that complete)\n- **Build docs:** `breeze build-docs`\n- **Determine which tests to run based on changed files:** `breeze ci selective-check --commit-ref <commit_with_squashed_changes>`\n<!-- END generated-commands, please keep comment here to allow auto update -->\n\nSQLite is the default backend. Use `--backend postgres` or `--backend mysql` for integration tests that need those databases. If Docker networking fails, run `docker network prune`.\n\n## Repository Structure\n\nUV workspace monorepo. Key paths:\n\n- `airflow-core/src/airflow/` — core scheduler, API, CLI, models\n  - `models/` — SQLAlchemy models (DagModel, TaskInstance, DagRun, Asset, etc.)\n  - `jobs/` — scheduler, triggerer, Dag processor runners\n  - `api_fastapi/core_api/` — public REST API v2, UI endpoints\n  - `api_fastapi/execution_api/` — task execution communication API\n  - `dag_processing/` — Dag parsing and validation\n  - `cli/` — command-line interface\n  - `ui/` — React/TypeScript web interface (Vite)\n- `task-sdk/` — lightweight SDK for Dag authoring and task execution runtime\n  - `src/airflow/sdk/execution_time/` — task runner, supervisor\n- `providers/` — 100+ provider packages, each with its own `pyproject.toml`\n- `airflow-ctl/` — management CLI tool\n- `chart/` — Helm chart for Kubernetes deployment\n- `dev/` — development utilities and scripts used to bootstrap the environment, releases, breeze dev env\n- `scripts/` — utility scripts for CI, Docker, and prek hooks (workspace distribution `apache-airflow-scripts`)\n  - `ci/prek/` — prek (pre-commit) hook scripts; shared utilities in `common_prek_utils.py`\n  - `tests/` — pytest tests for the scripts; run with `uv run --project scripts pytest scripts/tests/`\n\nThe `uv.lock` file is generated by `uv lock`, `uv sync` and is committed to the repo - it contains snapshot of\nversions of all dependencies used for development of Airflow. If at any point in time you have a conflict\nwith `uv.lock`, simply delete it and run `uv lock` to regenerate it.\n\n\n## Architecture Boundaries\n\n1. Users author Dags with the Task SDK (`airflow.sdk`).\n2. Dag File Processor parses Dag files in separate processes and stores serialized Dags in the metadata DB. Software guards prevent individual parsing processes from accessing the database directly and enforce use of the Execution API, but these guards do not protect against intentional bypassing by malicious or misconfigured code.\n3. Scheduler reads serialized Dags — **never runs user code** — and creates Dag runs / task instances.\n4. Workers execute tasks via Task SDK and communicate with the API server through the Execution API — **never access the metadata DB directly**. Each task receives a short-lived JWT token scoped to its task instance ID.\n5. API Server serves the React UI and handles all client-database interactions.\n6. Triggerer evaluates deferred tasks/sensors in separate processes. Like the Dag File Processor, software guards steer it through the Execution API rather than direct database access, but these guards do not protect against intentional bypassing by malicious or misconfigured code.\n7. Shared libraries that are symbolically linked to different Python distributions are in `shared` folder.\n8. Airflow uses `uv workspace` feature to keep all the distributions sharing dependencies and venv\n9. Each of the distributions should declare other needed distributions: `uv --project <FOLDER> sync` command acts on the selected project in the monorepo with only dependencies that it has\n\n## Security Model\n\nWhen reviewing code, writing security documentation, or performing security research, keep in\nmind the following aspects of Airflow's security model. The authoritative reference is\n[`airflow-core/docs/security/security_model.rst`](airflow-core/docs/security/security_model.rst)\nand [`airflow-core/docs/security/jwt_token_authentication.rst`](airflow-core/docs/security/jwt_token_authentication.rst).\n\nParticularly, the intentional design choices, that are not security vulnerabilities and should not be\nreported as such are described in \"What is NOT considered a security vulnerability\" chapter of the security model.\n\n**When flagging security concerns, distinguish between:**\n\n1. **Actual vulnerabilities** — code that violates the documented security model (e.g., a worker\n   gaining database access it shouldn't have, a Scheduler executing user code, an unauthenticated\n   user accessing protected endpoints).\n2. **Known limitations** — documented gaps where the current implementation doesn't provide full\n   isolation (e.g., DFP/Triggerer database access, shared Execution API resources, multi-team\n   not enforcing task-level isolation). These are tracked for improvement in future versions and\n   should not be reported as new findings.\n3. **Deployment hardening opportunities** — measures a Deployment Manager can take to improve\n   isolation beyond what Airflow enforces natively (e.g., per-component configuration, asymmetric\n   JWT keys, network policies). These belong in deployment guidance, not as code-level issues.\n\n# Shared libraries\n\n- shared libraries provide implementation of some common utilities like logging, configuration where the code should be reused in different distributions (potentially in different versions)\n- we have a number of shared libraries that are separate, small Python distributions located under `shared` folder\n- each of the libraries has it's own src, tests, pyproject.toml and dependencies\n- sources of those libraries are symbolically linked to the distributions that are using them (`airflow-core`, `task-sdk` for example)\n- tests for the libraries (internal) are in the shared distribution's test and can be run from the shared distributions\n- tests of the consumers using the shared libraries are present in the distributions that use the libraries and can be run from there\n\n## Coding Standards\n\n- **Always format and check Python files with ruff immediately after writing or editing them:** `uv run ruff format <file_path>` and `uv run ruff check --fix <file_path>`. Do this for every Python file you create or modify, before moving on to the next step.\n- No `assert` in production code.\n- **Comment sparingly — code says *what*, comments say *why*.** Add a comment only when the reasoning is non-obvious and cannot be carried by a clear name or the code itself. Do not write narrating comments that restate the next line, do not pad logic with multi-line prose, and do not repeat the same rationale at several sites — put one concise note at the source of truth and let the others stand on their own. Tests whose names already describe intent need no explanatory comment. Reserve longer explanation for genuinely complex or non-obvious logic (e.g. a security check whose threat model isn't apparent), and keep even that as tight as it can be. Over-commenting is noise that ages badly and obscures the code it wraps.\n- `time.monotonic()` for durations, not `time.time()`.\n- In `airflow-core`, functions with a `session` parameter must not call `session.commit()`. Use keyword-only `session` parameters.\n- Imports at top of file. Valid exceptions: circular imports, lazy loading for worker isolation, `TYPE_CHECKING` blocks.\n- Guard heavy type-only imports (e.g., `kubernetes.client`) with `TYPE_CHECKING` in multi-process code paths.\n- Define dedicated exception classes or use existing exceptions such as `ValueError` instead of raising the broad `AirflowException` directly. Each error case should have a specific exception type that conveys what went wrong. **Never add new direct `raise AirflowException(...)` usages — the community is actively reducing them, not adding more, and the `check-no-new-airflow-exceptions` prek hook enforces this across `airflow-core`, `airflow-ctl`, `task-sdk`, `providers`, and `shared`.** Prefer a Python built-in (`ValueError`, `TypeError`, `OSError`, …) or a dedicated class in the appropriate `exceptions.py`. The only acceptable way an `AirflowException` line may move is relocating an already-existing one verbatim during a refactor (e.g. moving code between files) — that is not a new usage. When you touch code that already raises `AirflowException`, prefer narrowing it to a more specific exception rather than leaving or duplicating it.\n- Translate domain-layer exceptions to `HTTPException` at FastAPI route boundaries. In `airflow-core/src/airflow/core_api/` route handlers, catch errors raised by domain code (e.g., `ValueError` from `airflow.state.metastore.MetastoreStateBackend` for a missing row or invalid input) and re-raise as `HTTPException` with the right status (`404` for not-found, `400` for invalid input). Otherwise they propagate as `500 Internal Server Error`, leaking internals and misleading clients.\n- Bulk `DELETE`/`UPDATE` in the scheduler loop or any synchronous interval task (e.g. `call_regular_interval` callbacks) must be batched with `LIMIT` and committed between batches — never issue a single unbounded bulk write against a user-driven table. Unbounded bulk writes hold row locks for the entire transaction (blocking concurrent writers) and stall the scheduler main loop. Filter columns used by the cleanup must be indexed. Follow the batching pattern in `airflow-core/src/airflow/utils/db_cleanup.py`.\n- Name functions and methods with action verbs: `get_`, `extract_`, `find_`, `compute_`, `build_`, etc. Avoid noun-only names like `_serialize_keys` or `_base_names` — they read as attributes, not callables. Predicates (`is_`, `has_`) are the one exception.\n- Apache License header on all new files (prek enforces this).\n- **Keep selective-checks behaviour and its documentation in sync.** The CI optimisation logic lives in `dev/breeze/src/airflow_breeze/utils/selective_checks.py` (run-mode decisions, file-group matching, test-type selection, prek-hook skipping). Whenever you change a rule there — add/rename a file group, change what forces `full_tests_needed`/`all_versions`, alter how providers or test types are selected, or change which prek hooks are skipped — update [`dev/breeze/doc/ci/04_selective_checks.md`](dev/breeze/doc/ci/04_selective_checks.md) in the same PR (the decision-rules list, the diagrams, the outputs table, and the worked examples as applicable) and add/adjust tests in `dev/breeze/tests/test_selective_checks.py`. The doc is the human-facing explanation of that file; letting them drift makes CI behaviour impossible to reason about.\n- Newsfragments are only used by distributions whose release process consumes them via towncrier — currently `airflow-core/newsfragments/`, `chart/newsfragments/`, and `dev/mypy/newsfragments/` — and only for major or breaking changes. **Golden rule: never create a newsfragment unless you are certain the change is user-facing.** If you are not sure the change is visible to users — build/release tooling, CI, packaging, internal refactors with no behavior change, dev-only scripts, and test-only changes are *not* user-facing — do **not** add one. Default to omitting it; a maintainer will ask for a newsfragment during review if the change warrants one. Adding a spurious newsfragment for a non-user-facing change is a defect, not a safe default. **Never add newsfragments for `providers/` or `airflow-ctl/`** — those distributions are released from `main` and their release managers regenerate the changelog from `git log`, so per-PR newsfragments are not consumed (see `dev/README_RELEASE_PROVIDERS.md` and `dev/README_RELEASE_AIRFLOWCTL.md`). For a user-visible note in those distributions, edit the changelog directly: `providers/<provider>/docs/changelog.rst` for providers, `airflow-ctl/RELEASE_NOTES.rst` for airflow-ctl. Changes to `task-sdk/` ship in `airflow-core` — use `airflow-core/newsfragments/`.\n\n## Testing Standards\n\n- Target exactly 100% coverage of what the PR changes — no more, no less. Every changed or added behaviour must have a test; every test must fail without the PR's change. Do not add tests for pre-existing logic that was already present before the PR, and do not test standard-library or third-party functions. The exception is deliberate behaviour or integration tests, which may cross those boundaries by design.\n- Use pytest patterns, not `unittest.TestCase`.\n- Use `spec`/`autospec` when mocking.\n- Prefer `@mock.patch` decorators over `with mock.patch(...)` context managers for patching. Use `conf_vars` (from `tests_common.test_utils.config`) for Airflow config overrides — as a decorator when the value is fixed, as a context manager when it varies via `@pytest.mark.parametrize`.\n- Use `time_machine` for time-dependent tests. Do not use `datetime.now()`\n- Use `@pytest.mark.parametrize` for multiple similar inputs — consolidate tests that only differ in input/expected values into a single parametrized test.\n- Use `@pytest.mark.db_test` for tests that require database access.\n- Test fixtures: `devel-common/src/tests_common/pytest_plugin.py`.\n- Test location mirrors source: `airflow/cli/cli_parser.py` → `tests/cli/test_cli_parser.py`.\n- Do not assert on raw log text (`caplog.text` for example), these are legacy string matching APIs planned for removal. Structured assertions via `caplog` (which resolves to `cap_structlog` under structlog) are fine and preferred: `\"event name\" in caplog` or `{\"event\": ..., \"field\": ...} in caplog`.\n\n## Output conventions\n\n- Put any files you generate (PR reviews, reports, scratch output) under `files/`.\n- Create `files/` if it doesn't exist.\n\n\n## Commits and PRs\n\nWrite commit messages focused on user impact, not implementation details.\n\n- **Good:** `Fix airflow dags test command failure without serialized Dags`\n- **Good:** `UI: Fix Grid view not refreshing after task actions`\n- **Bad:** `Initialize Dag bundles in CLI get_dag function`\n- **Bad:** `fix(cli): dags test failure` — Airflow does not use Conventional Commits\n  (`feat:`, `fix:`, `chore:` …). Write the subject as plain prose. A `commit-msg`\n  prek hook (`check-no-conventional-commit-message`) rejects these, and CI checks\n  every commit of the PR.\n\n**Always run `prek install` before committing any code.** It installs the\n`commit-msg` hook (in addition to `pre-commit`) so the Conventional Commits guard\nruns locally; a clone that ran `prek install` before this hook existed must re-run\nit to pick up the new hook type.\n\nUse the **imperative mood** and a plain message — do **not** use Conventional Commits prefixes\n(`fix:`, `feat:`, `chore:`, `docs:`, `refactor:`, …). apache/airflow does not follow that\nconvention. (Area tags the project already uses, like `UI:` / `API:` / `Helm:`, are fine;\nConventional-Commit `type:` tokens are not.) The same rule applies to PR titles.\n\nThe commit message **body** should describe **why** the change is made — the motivation and\ncontext — and **never what** the change is. The diff already shows what changed; restating it in\nprose adds noise.\n\nFor `airflow-core` (and `chart/`, `dev/mypy/`) **user-facing** changes, add a newsfragment in that distribution's `newsfragments/` directory. **Golden rule: only add a newsfragment when you are certain the change is visible to users; when in doubt, do not add one** — a maintainer will request one in review if it is needed. Build/release tooling, CI, packaging, internal refactors, and dev-only scripts are not user-facing and must not get a newsfragment:\n`echo \"Brief description\" > airflow-core/newsfragments/{PR_NUMBER}.{bugfix|feature|improvement|doc|misc|significant}.rst`\n\n**Do not add newsfragments for `providers/` or `airflow-ctl/`** — their release managers regenerate the changelog from `git log` and do not consume newsfragments. Update the changelog directly when needed: `providers/<provider>/docs/changelog.rst` (see `providers/AGENTS.md`) or `airflow-ctl/RELEASE_NOTES.rst`. Changes to `task-sdk/` use `airflow-core/newsfragments/` since task-sdk ships in airflow-core.\n\n- NEVER add Co-Authored-By with yourself as co-author of the commit. Agents cannot be authors, humans can be, Agents are assistants.\n\n### Git remote naming conventions\n\nAirflow standardises on two git remote names, and the rest of this file, the\ncontributing docs, and the release docs all assume them:\n\n- **`upstream`** — the canonical `apache/airflow` repository (fetch from here).\n- **`origin`** — the contributor's fork of `apache/airflow` (push PR branches here).\n\nAlways push branches to `origin`. Never push directly to `upstream` (and never\npush directly to `main` on either remote).\n\n**Before running any remote-based command, run `git remote -v` and verify the\nnames match this convention.** If they do not — for example, the upstream remote\nis called `apache`, or `origin` points at `apache/airflow` with the fork under a\ndifferent name like `fork` — **do not silently go along with the existing\nnames**. Surface the mismatch to the user and propose the exact rename commands\nto bring the checkout in line with the convention, then ask the user to confirm\nbefore running them. Examples:\n\n- Upstream is named `apache`, fork is `origin` (common legacy layout):\n\n  ```bash\n  git remote rename apache upstream\n  ```\n\n- `origin` points at `apache/airflow` and the fork is named `fork` (release-manager\n  / \"cloned upstream directly\" layout):\n\n  ```bash\n  git remote rename origin upstream\n  git remote rename fork origin\n  ```\n\n- Upstream is missing entirely:\n\n  ```bash\n  git remote add upstream https://github.com/apache/airflow.git\n  # or, for SSH:\n  git remote add upstream git@github.com:apache/airflow.git\n  ```\n\n- Fork is missing entirely:\n\n  ```bash\n  gh repo fork apache/airflow --remote --remote-name origin\n  ```\n\nAfter any rename/add, re-run `git remote -v` to confirm the new state before\ncontinuing with commands that assume `upstream` / `origin`.\n\nIf a doc, script, or command you're about to run uses the old `apache` name (or\nany other variant), **translate it to the `upstream` convention** in what you\npropose to the user, rather than perpetuating the old name. Flag the stale\ndocumentation so it can be fixed in a follow-up.\n\n### Before starting: check for an existing PR\n\nBefore working on an issue, check for open PRs already addressing it\n(`gh pr list --search \"<issue number or keywords>\"`, and look for `closes:`\n/ `fixes:` references). Airflow allows parallel work — \"better PR wins\"\n(see `contributing-docs/04_how_to_contribute.rst`) — but it is not the\ndefault: prefer reviewing and building on an existing PR. Open a separate\none only *if your approach is genuinely different*. Do not blindly open\nanother near-identical PR for an issue that already has one (or several) —\nthat just adds reviewer noise.\n\n### Creating Pull Requests\n\n**Always push to the user's fork (`origin`)**, not to `upstream` (`apache/airflow`).\nNever push directly to `main`.\n\nBefore pushing, confirm the remote setup matches the conventions above\n(`upstream` → `apache/airflow`, `origin` → your fork). Run `git remote -v` and,\nif the names don't match, propose renames as described in \"Git remote naming\nconventions\" — ask the user to confirm before running them.\n\nIf the fork remote does not exist at all, create one:\n\n```bash\ngh repo fork apache/airflow --remote --remote-name origin\n```\n\nBefore pushing, perform a self-review of your changes following the Gen-AI review guidelines\nin [`contributing-docs/05_pull_requests.rst`](contributing-docs/05_pull_requests.rst) and the\ncode review checklist in [`.github/instructions/code-review.instructions.md`](.github/instructions/code-review.instructions.md):\n\n1. Review the full diff (`git diff main...HEAD`) and verify every change is intentional and\n   related to the task — remove any unrelated changes.\n2. Read `.github/instructions/code-review.instructions.md` and check your diff against every\n   rule — architecture boundaries, database correctness, code quality, testing requirements,\n   API correctness, and AI-generated code signals. Fix any violations before pushing.\n3. Confirm the code follows the project's coding standards and architecture boundaries\n   described in this file.\n4. Run regular (fast) static checks (`prek run --from-ref <target_branch> --stage pre-commit`)\n   and fix any failures. This includes mypy checks for non-provider projects (airflow-core, task-sdk, airflow-ctl, dev, scripts, devel-common).\n5. Run manual (slower) checks\n   (`prek run --from-ref <target_branch> --stage manual --skip compile-ui-assets-dev --skip view-skill-eval --skip run-skill-eval-codex`)\n   and fix any failures. The skipped hooks start long-running local servers or provision the\n   opt-in Codex environment rather than run checks that complete.\n6. Run relevant individual tests and confirm they pass.\n7. Find which tests to run for the changes with selective-checks and run those tests in parallel to confirm they pass and check for CI-specific issues.\n8. Check for security issues — no secrets, no injection vulnerabilities, no unsafe patterns.\n\nBefore pushing, always rebase your branch onto the latest target branch (usually `main`)\nto avoid merge conflicts and ensure CI runs against up-to-date code:\n\n```bash\ngit fetch upstream <target_branch>\ngit rebase upstream/<target_branch>\n```\n\nIf there are conflicts, resolve them and continue the rebase. If the rebase is too complex,\nask the user for guidance.\n\nThen push the branch to your fork (`origin`) and open the PR creation page in the browser\nwith the body pre-filled (including the generative AI disclosure already checked):\n\n```bash\ngit push -u origin <branch-name>\ngh pr create --web --title \"Short title (under 70 chars)\" --body \"$(cat <<'EOF'\nBrief description of the changes.\n\ncloses: #ISSUE  (if applicable)\n\n---\n\n##### Was generative AI tooling used to co-author this PR?\n\n- [X] Yes — <Agent Name and Version>\n\nGenerated-by: <Agent Name and Version> following [the guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)\n\nEOF\n)\"\n```\n\nThe `--web` flag opens the browser so the user can review and submit. The `--body` flag\npre-fills the PR template with the generative AI disclosure already completed.\n\nRemind the user to:\n\n1. Review the PR title — keep it short (under 70 chars), in the imperative mood, and focused on user impact. Do not use Conventional Commits prefixes (`fix:`, `feat:`, `chore:`, …).\n2. Add a brief description of the changes at the top of the body.\n3. Reference related issues when applicable (`closes: #ISSUE` or `related: #ISSUE`).\n\n### Golden rule: when a fix is imminent, open the PR, not an issue\n\nIf you already know how to fix the problem and you (or the user) are going to\nopen the PR shortly, **do not file a GitHub issue first**. Go straight to the\nPR.\n\n- Airflow does not use issues as a changelog, as a parallel bug database, or\n  as a duplicate record of in-flight work. The PR itself is the canonical\n  record — title, description, diff, discussion, and merge all live in one\n  place. An issue that gets closed by a PR a day later is double accounting\n  that carries no information the PR does not already carry.\n- Open issues attract drive-by submissions, often from other agents, that\n  haven't seen the in-flight work. That produces duplicate fixes, low-quality\n  PRs that have to be closed, and wasted reviewer time. Not opening the\n  issue avoids creating that bait in the first place.\n- If you catch yourself drafting an issue body that reads like the PR\n  description you are about to write, that is the signal — skip the issue\n  and open the PR.\n\nThe one exception is the case covered by the next section: the PR ships a\n**workaround, mitigation, or partial fix** and the real follow-up work is\ngenuinely deferred to a later PR. There, the issue captures work that will\noutlive the PR, so the issue is load-bearing rather than duplicate.\n\n### Tracking issues for deferred work\n\nWhen a PR applies a **workaround, version cap, mitigation, or partial fix**\nrather than solving the underlying problem (for example: upper-binding a\ndependency to avoid a breaking upstream release, disabling a feature\nbehind a flag, reverting a change that needs a better replacement, or\npapering over a bug so a release can ship), the deferred work must be\ncaptured in a GitHub tracking issue **and** the tracking issue URL must\nappear as a comment at the workaround site in the code.\n\n1. **Open the tracking issue first**, before finalising the PR body.\n2. **Reference it in the PR body by number** — e.g. \"full migration is\n   tracked in #65609\" — so anyone reviewing the PR can see what was\n   deferred and why.\n3. **Add a link to the tracking issue as a comment at the workaround\n   itself**, so the reference survives after the PR merges and anyone\n   reading the source later can click straight through to the follow-up\n   work. Use the **full issue URL**, not bare `#NNNNN` — bare references\n   do not auto-link outside GitHub's web UI (e.g. when grepping in an\n   editor, browsing a checkout, or reading the file in a terminal).\n   For example:\n\n   ```toml\n   # pyproject.toml\n   # Remove the <1.0 cap after migrating to httpx 1.x;\n   # tracked at https://github.com/apache/airflow/issues/65609\n   \"httpx>=0.27.0,<1.0\",\n   ```\n\n   ```python\n   # some_module.py\n   # Delete this fallback once the new client is on all workers;\n   # tracked at https://github.com/apache/airflow/issues/65609\n   if old_client:\n       ...\n   ```\n\n4. **Do not** write vague forward-looking phrases like \"will open a\n   tracking issue\" or \"to be filed later\" in the PR body or in code\n   comments. Open the issue, link it in both places, then submit the PR.\n5. The tracking issue should describe: what the workaround is, why it\n   was chosen, the concrete follow-up work needed, and any acceptance\n   criteria for removing the workaround.\n\nIf a PR you already opened has such forward-looking language, open the\ntracking issue, add a PR comment referencing the issue URL, and push a\nfollow-up commit that adds the tracking-issue URL as a comment at the\nworkaround site in the code.\n\n### GitHub messages drafted by agents\n\nAnything an agent drafts that ends up posted to GitHub on the user's\naccount — PR / issue comments, PR-level reviews, line-level review\ncomments, discussion replies — must end with an attribution footer.\nThe footer is required whether or not a human reviewed the draft\nfirst; what changes between the two cases is the wording.\n\nPlace the footer on its own paragraph at the end of the message,\nseparated from the body by a blank line and a horizontal rule. Use\nthe same agent name string used in `Generated-by:` on PR bodies (for\nexample, `Claude Code (Opus 4.7)`).\n\n- **Agent draft, posted without prior human review** (autonomous /\n  routine work, scheduled triage, etc.):\n\n  ```\n  ---\n  Drafted-by: <Agent Name and Version> (no human review before posting)\n  ```\n\n- **Agent draft, reviewed and approved by a human maintainer before\n  posting:**\n\n  ```\n  ---\n  Drafted-by: <Agent Name and Version>; reviewed by @<github-handle> before posting\n  ```\n\n  The `@<github-handle>` is the human who actually read the draft\n  and said \"post it as-is\" (or similar). It is not the user the agent\n  is \"running on behalf of\" if no review took place — that case is the\n  first form, not this one.\n\nThis footer is in addition to, not a replacement for, any per-tool\ndisclosure rules (the PR body still keeps its own `Generated-by:`\nblock under the AI-disclosure checkbox; commit messages still follow\nthe no-self-as-co-author rule above). Do not skip the footer to\nshorten a message — attribution applies regardless of message length.\n\n#### Do not tag individuals\n\nAI agents MUST NOT mention or tag individual contributors, committers,\nPMC members, or maintainers using GitHub usernames (e.g. `@user`) unless\nexplicitly instructed by a human reviewer. When suggesting who might be\nrelevant to a discussion, refer to roles, teams, code ownership\ninformation, labels, or components instead of individuals. This keeps\nnotification noise down and avoids pulling people into threads they have\nnot chosen to join.\n\nThe only exceptions are mentions a human has explicitly authorized —\nincluding the `@<github-handle>` in the `Drafted-by: … reviewed by\n@<handle>` footer above, which names the reviewer who approved the\nmessage — and replying within a thread to people already actively\nparticipating in that same PR/issue discussion.\n\n## apache-magpie framework\n\nThis repo adopts the [`apache/magpie`](https://github.com/apache/magpie)\nframework via the snapshot mechanism. The framework provides the\n`pr-management-*` skills (triage, code-review, stats, mentor); they are\ngitignored symlinks into the `.apache-magpie/` snapshot directory.\n\nA fresh clone needs the snapshot populated before any framework skill is\ninvocable. Run `/magpie-setup` (or follow\n[`.claude/skills/magpie-setup/`](.claude/skills/magpie-setup/)) to fetch\nit per the committed [`.apache-magpie.lock`](.apache-magpie.lock). The\ncontributor-facing summary of the adoption + setup flow lives in the\n[Agent-assisted contribution section of `README.md`](README.md#agent-assisted-contribution-apache-magpie).\n\nAdopter-specific modifications to framework-skill workflows live in\n[`.apache-magpie-overrides/`](.apache-magpie-overrides/) — never edit\nthe snapshot directly. Framework changes go via PR to\n[`apache/magpie`](https://github.com/apache/magpie).\n\n### Reviewing pull requests\n\nWith apache-magpie installed locally, use the\n`magpie-pr-management-code-review` skill for PR code review. It posts\nfindings as **inline review comments** anchored to `file:line`, presented\n**individually for accept/skip** before anything is submitted — prefer it\nover an ad-hoc review pass or a generic review command. A body-only review\nis the explicit opt-out (`inline:off`).\n\n## Boundaries\n\n- **Ask first**\n  - Large cross-package refactors.\n  - New dependencies with broad impact.\n  - Destructive data or migration changes.\n- **Never**\n  - Commit secrets, credentials, or tokens.\n  - Edit generated files by hand when a generation workflow exists.\n  - Use destructive git operations unless explicitly requested.\n\n## References\n\n- [`contributing-docs/03a_contributors_quick_start_beginners.rst`](contributing-docs/03a_contributors_quick_start_beginners.rst)\n- [`contributing-docs/05_pull_requests.rst`](contributing-docs/05_pull_requests.rst)\n- [`contributing-docs/07_local_virtualenv.rst`](contributing-docs/07_local_virtualenv.rst)\n- [`contributing-docs/08_static_code_checks.rst`](contributing-docs/08_static_code_checks.rst)\n- [`contributing-docs/12_provider_distributions.rst`](contributing-docs/12_provider_distributions.rst)\n- [`contributing-docs/19_execution_api_versioning.rst`](contributing-docs/19_execution_api_versioning.rst)\n"}}