ethanstoner opened a new pull request, #72292:
URL: https://github.com/apache/airflow/pull/72292

   `croniter` accepts `?` in the day-of-month and day-of-week fields and 
expands it to `*` 
([`croniter.py`](https://github.com/kiorky/croniter/blob/master/src/croniter/croniter.py):
 *"currently just trade `?` as `*`"*), so `0 0 ? * MON` schedules exactly like 
`0 0 * * MON`. Airflow's docs explicitly point users at croniter's extended 
syntax in 
[`cron.rst`](https://github.com/apache/airflow/blob/main/airflow-core/docs/authoring-and-scheduling/cron.rst).
   
   `CronMixin._describe_with_dom_dow_fix` compares the DOM/DOW fields against 
`"*"` only, so a `?` counts as a restriction and the description gets split 
into a bogus "or" clause that claims a run which never happens.
   
   ### Reproduction (on `main`, before this change)
   
   ```
   $ uv run --project airflow-core python -c '...'
   croniter 6.2.4 | cron-descriptor 2.1.0
   
   expression  : 0 0 ? * MON
   actual runs : ['2026-01-05', '2026-01-12', '2026-01-19']    # Mondays only
   description : 'At 00:00 (or) At 00:00, only on Monday'      # <- claims a 
daily midnight run
   
   expression  : 0 0 1 * ?
   actual runs : ['2026-02-01', '2026-03-01', '2026-04-01']    # 1st of month 
only
   description : 'At 00:00, on day 1 of the month (or) At 00:00'
   ```
   
   The description is persisted as `DagModel.timetable_description` and 
surfaced in the UI, the REST API (`DAGDetailsResponse.timetable_description`) 
and `airflow dags details`, so the user is shown a schedule that contradicts 
what the scheduler actually does.
   
   ### After
   
   ```
   '0 0 ? * MON' -> 'At 00:00, only on Monday'
   '0 0 1 * ?'   -> 'At 00:00, on day 1 of the month'
   '0 0 15 * 1'  -> 'At 00:00, on day 15 of the month (or) At 00:00, only on 
Monday'
   ```
   
   Genuine DOM/DOW conflicts keep the existing "or" explanation added in #54644.
   
   ### Testing
   
   Three parametrized cases added to 
`airflow-core/tests/unit/timetables/test_cron_mixin.py`, each asserting the `?` 
form describes identically to its `*` equivalent. They fail on unpatched `main`:
   
   ```
   3 failed, 3 passed
   FAILED 
test_cron_mixin.py::test_question_mark_is_not_a_dom_dow_conflict[question-mark-day-of-month]
   FAILED 
test_cron_mixin.py::test_question_mark_is_not_a_dom_dow_conflict[question-mark-day-of-week]
   FAILED 
test_cron_mixin.py::test_question_mark_is_not_a_dom_dow_conflict[question-mark-both]
   ```
   
   and pass with the fix. Full suite and static checks:
   
   - `uv run --project airflow-core pytest airflow-core/tests/unit/timetables/` 
— **256 passed**
   - `prek run --files airflow-core/src/airflow/timetables/_cron.py 
airflow-core/tests/unit/timetables/test_cron_mixin.py` — all hooks pass
   - `prek run mypy-airflow-core --files 
airflow-core/src/airflow/timetables/_cron.py` — passed
   
   No newsfragment: this is a bugfix to a generated description string, not a 
significant user-facing change. Happy to add one if a maintainer prefers.
   
   ---
   
   ##### Was generative AI tooling used to co-author this PR?
   
   - [X] Yes (please specify the tool below)
   
   Generated-by: Claude Code following [the 
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
   
   The defect was found by auditing `airflow/timetables/`, reproduced against 
unmodified `main`, and every command and output quoted above was actually run 
locally. I have reviewed and understand the change; it is two lines of logic 
plus a test.
   


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