steveahnahn opened a new pull request, #71861:
URL: https://github.com/apache/airflow/pull/71861
Two Execution API response paths skip their version migrations, so a Task
SDK older than the API server either receives fields it forbids or gets a 500.
#70138 fixed this class for `/dag-runs/previous` and direct `DagRun` responses;
these are the two paths it did not reach.
### Root cause 1: the compat previous-run route has no response model
`get_previous_dagrun_compat` serves `/dag-runs/{dag_id}/previous` for
clients older than `2026-04-06`. It carries no return annotation, so the route
has no response model, and schema-based converters are matched against the
response model by identity. None of them fire.
The route therefore answers old clients with the current `DagRun` shape. At
`2025-08-10` the body carries `triggering_user_name`, `note`, `partition_key`,
`team_name` and `partition_date`, and `start_date` is `null` for a queued
previous run. The client model forbids unknown keys and requires a non-null
`start_date`, so `ti.get_previous_dagrun()` raises and the task fails.
#70138 already documented this identity-matching rule in `v2026_06_30.py`
and solved it for the sibling path with path-based converters. This applies the
same treatment to the compat path, in each `VersionChange` that touched
`DagRun` after the endpoint became compat-only.
### Root cause 2: the asset-events converter stops at the top level
`remove_partition_key_from_asset_events` strips `partition_key` from each
event but not from the Dag runs nested inside it. Those nested runs use a
strict schema too, so the field is not merely leaked: response serialisation
fails and the client receives
```
fastapi.exceptions.ResponseValidationError: 1 validation error:
{'type': 'extra_forbidden', 'loc': ('response', 'asset_events', 0,
'created_dagruns', 0, 'partition_key')}
```
This is a 500 on both `/asset-events/by-asset` and `/by-asset-alias` for
every old client whose events triggered a Dag run, which is the ordinary case
in asset-driven pipelines. The sibling converter in the same `VersionChange`
already walks nested `consumed_asset_events`, which is why the migration reads
as complete.
### Why the existing tests did not catch either
They assert only fields that exist in every version. The compat-route test
checks `dag_id`, `run_id` and `state`; the old-version asset-events test builds
events with no attached Dag runs, so the nested strict model is never
exercised. Both leaks pass CI today.
The new tests assert in both directions: a field must be absent for versions
older than the one that introduced it, and still present for the version that
added it, so a future change cannot over-strip. They run at `2025-08-10` and
`2025-11-05`, and cover both asset-events endpoints.
Beyond the test assertions, the released client model was extracted from git
and used to validate the actual response bodies: it rejects the current output
and accepts it after this change.
### On the general case
Neither leak is a mistake in the migration machinery; both are missing
registrations. Nothing in CI replays a supported version against the model a
released SDK actually uses, so this class stays invisible until a mixed-version
deployment hits it. A shared contract test that does that replay would close
the class, and I am happy to follow up with one if that seems worth having.
related: #70138
related: #71379
---
##### Was generative AI tooling used to co-author this PR?
- [X] Yes — Claude Code (Opus 5)
Generated-by: Claude Code (Opus 5) following [the
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]