HKUDS/Vibe-Trading · error · ValueError

schedule must be a non-empty string

Error message

schedule must be a non-empty string

What it means

validate_schedule accepts only a non-empty string — either a millisecond interval or a 5-field cron expression. Passing None, an empty string, whitespace-only input, or a non-string type (int, dict) fails this first shape check before any parsing is attempted.

Source

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

    Returns:
        ``True`` for a bare positive-integer interval, ``False`` otherwise
        (including malformed input, which the validators reject separately).
    """
    return bool(_INTERVAL_MS_RE.fullmatch(str(schedule).strip()))


def validate_schedule(schedule: str) -> None:
    """Raise ``ValueError`` when *schedule* is malformed.

    Args:
        schedule: Either a positive integer string (interval-ms) or a
            simplified 5-field cron expression.

    Raises:
        ValueError: When the schedule does not match either accepted form.
    """
    if not schedule or not isinstance(schedule, str):
        raise ValueError("schedule must be a non-empty string")

    if _INTERVAL_MS_RE.fullmatch(schedule.strip()):
        # 15 digits ≈ 31,000 years in milliseconds — anything longer is not a
        # usable interval and would only feed int() conversion of huge strings.
        if len(schedule.strip()) > 15:
            raise ValueError("interval is too large; expected at most 15 digits of milliseconds")
        return  # valid interval

    parts = schedule.strip().split()
    if len(parts) != _CRON_PARTS:
        raise ValueError(f"schedule must be a positive integer (ms) or a 5-field cron string; got: {schedule!r}")
    for part, (low, high) in zip(parts, CRON_BOUNDS):
        _validate_cron_field(part, low, high)


def validate_timezone_shape(tz: Optional[str]) -> None:
    """Raise ``ValueError`` when *tz* is not ``None`` or a non-empty string.

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Ensure schedule is a non-empty string before calling, e.g. '3600000' or '0 9 * * 1-5'.
  2. Convert numeric intervals to strings: str(int(timedelta(minutes=30).total_seconds()*1000)).
  3. Default missing config fields to a concrete schedule rather than forwarding None.

Example fix

# before
create_scheduled_run(schedule=None, ...)
# after
create_scheduled_run(schedule='0 9 * * 1-5', ...)
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(schedule, str) or not schedule.strip():
    schedule = '0 9 * * 1-5'  # sane default
create_scheduled_run(schedule=schedule, ...)

Type guard

def is_schedule_str(s) -> bool:
    return isinstance(s, str) and bool(s.strip())

Try / catch

try:
    create_scheduled_run(schedule=schedule, ...)
except ValueError as e:
    if 'non-empty string' in str(e):
        return bad_request('schedule is required')
    raise

Prevention

When it happens

Trigger: create_scheduled_run(schedule=None), schedule='' , schedule=3600000 (int instead of '3600000'), or an unset JSON field deserialized to None and forwarded directly.

Common situations: API payloads where the schedule field is omitted and defaults to None; YAML/JSON configs with a blank schedule key; passing a timedelta object that was never converted to ms.

Related errors


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