Skip to content

fix(reports): prevent bad-date crontab from crashing the report scheduler for all tenants - #42486

Merged
eschutho merged 1 commit into
apache:masterfrom
eschutho:fix-croniter-bad-date-scheduler
Jul 28, 2026
Merged

fix(reports): prevent bad-date crontab from crashing the report scheduler for all tenants#42486
eschutho merged 1 commit into
apache:masterfrom
eschutho:fix-croniter-bad-date-scheduler

Conversation

@eschutho

Copy link
Copy Markdown
Member

SUMMARY

A crontab expression that's syntactically valid but can never match a real calendar date (e.g. 0 0 30 2 * for Feb 30th, or 0 0 31 4 * for April 31st — April only has 30 days) passes API schema validation on report/alert create/update, because superset/reports/schemas.py::validate_crontab() only checks croniter.is_valid(), which is a purely syntactic check.

Once such a schedule is saved as active, the celery-beat task reports.scheduler (superset/tasks/scheduler.py) iterates over every active ReportSchedule and calls cron_schedule_window() (superset/tasks/cron_util.py) for each, with no per-schedule isolation:

for active_schedule in active_schedules:
    for schedule in cron_schedule_window(
        triggered_at, active_schedule.crontab, active_schedule.timezone
    ):

Inside cron_schedule_window, iterating crons.all_next(datetime) raises croniter.CroniterBadDateError for a bad-date cron (croniter's internal max-year search gives up), uncaught anywhere in the chain. Because the loop has no per-schedule isolation, one report/alert with a bad-but-syntactically-valid crontab crashes the scheduler task for every other active schedule too — on every celery-beat tick, across the whole instance — until someone finds and fixes/deletes the offending schedule directly in the database.

PROBLEM

>>> from croniter import croniter
>>> from datetime import datetime, timezone
>>> croniter.is_valid("0 0 30 2 *")
True
>>> next(iter(croniter("0 0 30 2 *", datetime(2026, 7, 27, tzinfo=timezone.utc)).all_next(datetime)))
croniter.croniter.CroniterBadDateError: failed to find next date

CroniterBadDateError is a subclass of CroniterError, itself a subclass of ValueError — a raw library exception with no Superset-level handling on this path.

FIX

cron_schedule_window() already has an established precedent for degrading gracefully on bad per-schedule input a few lines above this bug: it catches UnknownTimeZoneError for an invalid timezone string and falls back to UTC with a logger.warning. This PR follows the same convention for the cron expression itself: the crons.all_next(datetime) iteration is now wrapped in try/except CroniterBadDateError, logging an error (this case is not a graceful degrade like the timezone fallback — it means the schedule can never fire until someone fixes it, so it's logged loudly) and letting the generator end instead of propagating and crashing the whole scheduler run.

This is a background celery-task loop, not an HTTP request/response path, so there's no 4xx/500 status to map to — the fix intentionally does not introduce a new superset/exceptions.py subclass (unlike the API-layer fixes in the related PRs below).

TESTING INSTRUCTIONS

New parametrized test test_cron_schedule_window_invalid_cron_date added to tests/unit_tests/tasks/test_cron_util.py, covering "0 0 30 2 *" (Feb 30th) and "0 0 31 4 *" (April 31st), asserting cron_schedule_window() returns [] instead of raising.

Verified empirically both ways:

  • Pre-fix (fix stashed, test kept): both new cases fail with an uncaught croniter.croniter.CroniterBadDateError: failed to find next date propagating out of the test.
  • Post-fix: full tests/unit_tests/tasks/test_cron_util.py — 36 passed, no regressions to the timezone-fallback or normal-schedule cases.

ruff check / ruff format --check clean on both changed files (verified against both the repo's ambient ruff and the CI-pinned ruff==0.9.7 via uvx). mypy shows the same pre-existing missing-stub errors (croniter, pytz, freezegun.api.FakeDatetime) before and after — no new errors introduced.

Tradeoffs

Additive-only: a cron that can never fire now logs an error and produces zero scheduled executions instead of crashing the whole scheduler task. No change to the failure mode for any valid crontab.

ADDITIONAL INFORMATION

Same bug class / cleanup series as #42366, #42401, #42426, #42442 (raw system/library exception reaching a live code path instead of being handled) — this one surfaces in the report-scheduling background task rather than an API request path.

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration
  • Introduces new feature or API
  • Removes existing feature or API

…l reports

A syntactically valid but calendrically impossible crontab (e.g. "0 0 30 2 *"
for Feb 30th) passes API schema validation via croniter.is_valid(), which
only checks syntax. When the reports.scheduler celery-beat task later
iterates all_next() for such a schedule, croniter raises an uncaught
CroniterBadDateError, breaking the scheduler loop for every active report
and alert across the instance until the offending schedule is fixed
directly in the database.

cron_schedule_window() now catches CroniterBadDateError the same way it
already handles an invalid timezone: log the error and treat it as
producing no upcoming executions, isolating the bad schedule instead of
crashing the shared scheduler loop.
@dosubot dosubot Bot added alert-reports Namespace | Anything related to the Alert & Reports feature global:error Related to global errors affecting the platform labels Jul 27, 2026
@bito-code-review

bito-code-review Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #ee4e2a

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 1e3fc00..1e3fc00
    • superset/tasks/cron_util.py
    • tests/unit_tests/tasks/test_cron_util.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.50000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.24%. Comparing base (dece457) to head (1e3fc00).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
superset/tasks/cron_util.py 62.50% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #42486      +/-   ##
==========================================
- Coverage   65.25%   65.24%   -0.01%     
==========================================
  Files        2795     2795              
  Lines      157639   157642       +3     
  Branches    36052    36052              
==========================================
- Hits       102860   102858       -2     
- Misses      52802    52806       +4     
- Partials     1977     1978       +1     
Flag Coverage Δ
hive 38.38% <12.50%> (-0.01%) ⬇️
mysql 57.55% <62.50%> (-0.01%) ⬇️
postgres 57.58% <62.50%> (-0.01%) ⬇️
presto 40.30% <12.50%> (-0.01%) ⬇️
python 58.99% <62.50%> (-0.01%) ⬇️
sqlite 57.21% <62.50%> (-0.01%) ⬇️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@eschutho
eschutho requested a review from rebenitez1802 July 27, 2026 17:05

@rebenitez1802 rebenitez1802 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@eschutho
eschutho merged commit 4a4f068 into apache:master Jul 28, 2026
69 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

alert-reports Namespace | Anything related to the Alert & Reports feature global:error Related to global errors affecting the platform size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants