HKUDS/Vibe-Trading · error · ValueError
cron schedule has no matching time within search window: {sc
Error message
cron schedule has no matching time within search window: {schedule!r} What it means
_next_cron_due scans forward through days (and each matching hour/minute in the schedule's timezone) to find the next fire time after a given instant. If the search window contains no resolvable instant — e.g. every candidate wall time falls into a DST spring-forward gap and is skipped — the function gives up with this error instead of looping forever.
Source
Thrown at agent/src/scheduled_research/executor.py:161
minutes, hours, doms, months, dows = (
parse_cron_field(part, low, high) for part, (low, high) in zip(schedule.split(), CRON_BOUNDS)
)
zone = timezone.utc if tz is None else ZoneInfo(tz)
# Walk candidates on the *local* calendar of ``zone`` so field matching —
# the weekday in particular — follows the authoring wall clock. Ascending
# wall order maps to ascending UTC order under a fixed fold policy, so
# "strictly after" stays a plain epoch comparison.
day = datetime.fromtimestamp(after_ms / 1000.0, zone).date()
for offset in range(_CRON_SEARCH_LIMIT_DAYS):
candidate_day = day + timedelta(days=offset)
if not _day_matches(candidate_day, doms, months, dows):
continue
for hour in sorted(hours) if hours is not None else range(24):
for minute in sorted(minutes) if minutes is not None else range(60):
fire_ms = _local_wall_time_to_epoch_ms(candidate_day, hour, minute, zone)
if fire_ms is not None and fire_ms > after_ms:
return fire_ms
raise ValueError(f"cron schedule has no matching time within search window: {schedule!r}")
def _local_wall_time_to_epoch_ms(day: date, hour: int, minute: int, zone: tzinfo) -> int | None:
"""Resolve one local wall time to a UTC epoch-ms instant.
DST policy (#953): a nonexistent wall time — the spring-forward gap —
returns ``None`` so the occurrence is skipped; ``ZoneInfo`` would
otherwise silently map it past the transition (PEP 495) and run it. An
ambiguous wall time — the fall-back fold — resolves with ``fold=0``, the
first occurrence, so it runs exactly once.
"""
local = datetime(day.year, day.month, day.day, hour, minute, tzinfo=zone)
as_utc = local.astimezone(timezone.utc)
if as_utc.astimezone(zone).replace(tzinfo=None) != local.replace(tzinfo=None):
return None # wall time does not exist in this zone (DST gap)
return int(as_utc.timestamp() * 1000)
View on GitHub (pinned to 80ffdda44c)
Solutions
- Pick a fire time that exists year-round in the job's timezone (avoid 02:00-03:00 style hours near common DST shifts).
- Run jobs in UTC unless local-time firing is required.
- If the hour must be local, schedule a minute that is not skipped (e.g. 02:45 often survives a 1-hour gap shift).
Example fix
# before schedule = '0 2 * * *'; tz = 'America/Sao_Paulo' # after schedule = '0 4 * * *'; tz = 'America/Sao_Paulo'
Defensive patterns
Strategy: validation
Validate before calling
from scheduled_research.models import validate_schedule
validate_schedule(schedule)
# avoid fire hours inside common DST gaps
assert not (schedule.startswith('0 2 ') or schedule.startswith('0 30 2')) Try / catch
try:
due = job.next_due(after_ms)
except ValueError as e:
if 'no matching time' in str(e):
mark_job_schedule_failed(job) # per-job failure, not a crash Prevention
- Avoid scheduling at 02:00-03:00 local time.
- Prefer UTC for machine-driven jobs.
- Smoke-test every cron+tz pair with next_due at job creation.
When it happens
Trigger: A cron schedule whose only fire times are wall times that never exist (e.g. '0 2 * * *' in a timezone where 02:00 is always inside the spring-forward gap on relevant days), or pathological schedules combined with an after_ms far in the future beyond the scan window.
Common situations: Timezones like America/Sao_Paulo or historical zones whose DST transitions skip the scheduled hour; custom or misconfigured tz databases on the host; a schedule generated programmatically that pins an impossible hour.
Related errors
- cron field {part!r} is out of range; expected {low}-{high}
- cron field {part!r} is not valid; each field must be *, */n,
- cron range {atom!r} is reversed; expected low-high
- cron field {atom!r} is out of range; expected {low}-{high}
- schedule must be a non-empty string
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/ba4bc0f194e69a44.
Report an issue: GitHub.