HKUDS/Vibe-Trading · error · ValueError

cron range {atom!r} is reversed; expected low-high

Error message

cron range {atom!r} is reversed; expected low-high

What it means

Within a cron field atom, a range low-high must have low <= high. A reversed range like 5-1 matches the atom grammar but denotes an empty set of values, which is almost always an authoring mistake, so it is rejected explicitly rather than silently matching nothing.

Source

Thrown at agent/src/scheduled_research/models.py:64

    """Raise ``ValueError`` when one cron field is malformed or out of range."""
    if part == "*":
        return
    if _CRON_STEP_RE.fullmatch(part):
        value = int(part[2:])
        if not low <= value <= high:
            raise ValueError(f"cron field {part!r} is out of range; expected {low}-{high}")
        return
    for atom in part.split(","):
        match = _CRON_ATOM_RE.fullmatch(atom)
        if match is None:
            raise ValueError(
                f"cron field {part!r} is not valid; each field must be *, */n, "
                f"or a comma-separated list of numbers and low-high ranges"
            )
        start = int(match.group(1))
        end = int(match.group(2)) if match.group(2) is not None else start
        if start > end:
            raise ValueError(f"cron range {atom!r} is reversed; expected low-high")
        if start < low or end > high:
            raise ValueError(f"cron field {atom!r} is out of range; expected {low}-{high}")


def parse_cron_field(part: str, low: int, high: int) -> Optional[Set[int]]:
    """Expand one validated cron field into its matching values.

    Kept beside :func:`validate_schedule` so the accepted grammar and the
    executor's evaluation of it can never drift apart.

    Args:
        part: One whitespace-delimited cron field (already validated).
        low: Inclusive lower bound of the field.
        high: Inclusive upper bound of the field.

    Returns:
        The set of matching integer values, or ``None`` for the ``*`` wildcard.
    """

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Split wrap-around ranges into explicit lists: '22-23,0-2' for 22:00-02:00, '0,5-6' for Sunday plus Fri-Sat.
  2. Sort generated bounds before formatting a range.
  3. Prefer listing individual values when unsure.

Example fix

# before
validate_schedule('0 22-2 * * *')
# after
validate_schedule('0 22-23,0-2 * * *')
Defensive patterns

Strategy: validation

Validate before calling

for atom in part.split(','):
    if '-' in atom:
        lo, hi = map(int, atom.split('-'))
        assert lo <= hi, f'reversed range: {atom}'
validate_schedule(schedule)

Try / catch

try:
    validate_schedule(schedule)
except ValueError as e:
    return {'error': str(e)}, 400

Prevention

When it happens

Trigger: validate_schedule('0 0 * * 5-1') (Friday-to-Monday style wrap), '50-10 * * * *' for minutes, or ranges generated by min/max arguments passed in the wrong order.

Common situations: Trying to express a wrap-around range (e.g. 'weekend plus Monday' or '10pm-2am') which standard cron cannot express as a single range; programmatic range building with swapped bounds.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/450e6d50e9094e38. Report an issue: GitHub.