apa-pbraswell opened a new issue, #67970:
URL: https://github.com/apache/airflow/issues/67970

   ### Under which category would you file this issue?
   
   Airflow Core
   
   ### Apache Airflow version
   
   3.2.2
   
   ### What happened and how to reproduce it?
   
   ### HTTP 500 on GET /api/v2/dags/~/dagRuns/~/taskInstances after sorting by 
run_after column (regression in 3.2.2)
   
   **Airflow version:** 3.2.2 (regression from 3.2.1)
   
   ---
   
   #### Steps to reproduce
   
   1. Open the Task Instances page.
   2. Click the **run_after** column header to sort by it.
   3. Navigate to any Task Instances view with more than `limit` rows (default 
50).
   
   The page returns HTTP 500. The sort preference is persisted to 
`localStorage` under the key `task_instances-table-sort` as 
`[{"desc":true,"id":"run_after"}]`, so the failure recurs on every subsequent 
page load for any user who has clicked that column — including after a browser 
refresh.
   
   **Affected request:**
   `GET 
/api/v2/dags/~/dagRuns/~/taskInstances?cursor=&limit=50&order_by=-run_after`
   
   Single-page results (fewer than `limit` rows) return 200. The failure only 
occurs when the result set requires a second page (`has_next = true`).
   
   ---
   
   #### Exception
   
   ```
   NotImplementedError: Cursor pagination does not support column-form 
``to_replace``
   mapping for ``run_after``. Use a string alias in ``to_replace`` or sort by a
   primary-model attribute.
     File ".../api_fastapi/core_api/routes/public/task_instances.py", line 623, 
in get_task_instances
     File ".../api_fastapi/common/cursors.py", line 106, in encode_cursor
     File ".../api_fastapi/common/parameters.py", line 621, in row_value
   ```
   
   ---
   
   #### Root cause
   
   The bug was introduced by commit `4da0b664bd` (PR #65604 / backport #65746,
   "Add cursor based pagination for get_dag_runs endpoint"), which:
   
   1. Added a `row_value()` method to `SortParam` that raises 
`NotImplementedError`
      for column-form `to_replace` entries (where the value is a SQLAlchemy 
`Column`
      object rather than a string alias).
   2. Changed `encode_cursor()` in `cursors.py` to call 
`sort_param.row_value(row, attr_name)`
      instead of the prior `getattr(row, attr_name, None)`.
   
   The `get_task_instances` endpoint's `SortParam` has column-form `to_replace` 
entries:
   
   ```python
   to_replace={
       "logical_date":        DagRun.logical_date,
       "run_after":           DagRun.run_after,       # ← triggers the error
       "data_interval_start": DagRun.data_interval_start,
       "data_interval_end":   DagRun.data_interval_end,
   }
   ```
   
   The commit's own TODO comment incorrectly asserted *"no endpoint that uses 
cursor
   pagination needs [column-form to_replace] today"* — but `get_task_instances` 
already
   had cursor pagination (added in PR #64845 / #65405) and already had these
   column-form entries.
   
   The prior `getattr(row, "run_after", None)` worked because 
`TaskInstance.run_after`
   is declared as `association_proxy("dag_run", "run_after")` (taskinstance.py 
line 649),
   so attribute access on an eagerly-loaded TI row resolves the value through 
the join.
   The new `row_value()` path short-circuits before reaching `getattr`.
   
   `encode_cursor()` is only called when `has_next = True` (to build 
`next_cursor`).
   Single-page results never call it, which is why the error is page-count 
dependent.
   
   All four column-form keys are affected if sorted: `run_after`, 
`logical_date`,
   `data_interval_start`, `data_interval_end`.
   
   ---
   
   #### Proposed fix
   
   In `airflow/api_fastapi/common/parameters.py`, change `row_value()` to fall 
back
   to `getattr` for column-form mappings:
   
   ```python
   def row_value(self, row: Any, name: str) -> Any:
       if self.to_replace:
           replacement = self.to_replace.get(name)
           if isinstance(replacement, str):
               return getattr(row, replacement, None)
           if replacement is not None:
               # Column-form: fall back to getattr, which resolves association
               # proxies on the primary model (e.g. TaskInstance.run_after).
               return getattr(row, name, None)
       return getattr(row, name, None)
   ```
   
   ---
   
   #### Workaround
   
   **Per-user (immediate):** Clear the `task_instances-table-sort` key from
   `localStorage` in browser DevTools. This resets the sort to the UI default 
(`-id`),
   which is not in `to_replace` and does not trigger the error. The fix is lost 
if the
   user clicks the `run_after` column header again.
   
   **Server-side:** Omit or strip the `cursor` query parameter from requests to 
this
   endpoint. Without `cursor`, the endpoint uses offset pagination, which never 
calls
   `encode_cursor`. Sorting by a pure `TaskInstance` attribute (`id`, `state`,
   `start_date`, `map_index`) also avoids the error.
   
   ### What you think should happen instead?
   
   _No response_
   
   ### Operating System
   
   _No response_
   
   ### Deployment
   
   Official Apache Airflow Helm Chart
   
   ### Apache Airflow Provider(s)
   
   _No response_
   
   ### Versions of Apache Airflow Providers
   
   _No response_
   
   ### Official Helm Chart version
   
   Not Applicable
   
   ### Kubernetes Version
   
   _No response_
   
   ### Helm Chart configuration
   
   _No response_
   
   ### Docker Image customizations
   
   _No response_
   
   ### Anything else?
   
   This is probably obvious, but the above analysis was created by Claude 
Sonnet 4.6 and Datadog Bits, with my input, and validation of the browser-side 
workaround.
   
   ### Are you willing to submit PR?
   
   - [ ] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's [Code of 
Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)
   


-- 
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]

Reply via email to