#7780·redash

Malformed schedule dict silently disables scheduling with no visible error

Author: lkraiderCreated Aug 3, 2026Updated Aug 3, 2026

Summary

Query.outdated_queries() (redash/models/__init__.py) can silently set a query's schedule.disabled = True when the schedule JSON is malformed — with zero indication anywhere (API response, UI, logs visible to the org admin) that anything went wrong. The only symptom is stale query results, discovered much later.

Root cause

The schedule column has no server-side shape validation on write — POST /api/queries/{id} with {"schedule": {...}} accepts and echoes back whatever JSON is sent, even a partial object like {"interval": 86400}.

But the scheduler's outdated_queries() reads the schedule with direct dict indexing, not .get():

python
if query.schedule["until"]:
    ...
if should_schedule_next(
    retrieved_at, now,
    query.schedule["interval"],
    query.schedule["time"],
    query.schedule["day_of_week"],
    query.schedule_failures,
):

and should_schedule_next() parses time with a strict unpack:

python
hour, minute = time.split(":")

So on the very next scheduler tick after saving an incomplete schedule (missing until/time/day_of_week), or one where time includes seconds ("07:00:00" instead of "07:00"), either a KeyError or ValueError is raised. That exception is caught by a bare except Exception in outdated_queries():

python
except Exception as e:
    query.schedule["disabled"] = True
    db.session.commit()
    message = ("Could not determine if query %d is outdated due to %s. "
               "The schedule for this query has been disabled." % (query.id, repr(e)))
    logging.info(message)
    sentry.capture_exception(...)

This is only logging.info + Sentry (if configured) — nothing is exposed via the query API, the dashboard, or any user-facing surface. A schedule that looked fine seconds earlier silently flips to disabled: true within one scheduler tick (~a minute), and stays that way until someone happens to notice stale data and manually re-inspects the schedule JSON.

Impact

Anyone driving schedules through the API (rather than exclusively the UI, which always sends the full shape) can trip this without realizing it — we hit it across 9 separate queries feeding two production dashboards, and it recurred after an initial "fix" because the repair itself re-wrote an incomplete schedule object.

Suggested fixes (any of these would help)

  1. Validate schedule shape on POST /api/queries/{id} and reject/normalize incomplete or malformed objects instead of accepting them silently, or
  2. Use .get() with defaults in outdated_queries() / should_schedule_next() instead of direct indexing, so a partial schedule just runs with sane defaults rather than crashing, or
  3. At minimum, surface the auto-disable somewhere visible — e.g. include schedule_failures-style info in the GET /api/queries/{id} response, or a dashboard/query-list badge — so it isn't purely a server log line.

Version

Encountered against a Redash instance following the master-branch outdated_queries() implementation as of 2026-08.