{"record":{"id":"96f367fdee4fc551","repo":"HKUDS/Vibe-Trading","slug":"schedule-must-be-a-non-empty-string","errorCode":null,"errorMessage":"schedule must be a non-empty string","messagePattern":"schedule must be a non-empty string","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/scheduled_research/models.py","lineNumber":126,"sourceCode":"    Returns:\n        ``True`` for a bare positive-integer interval, ``False`` otherwise\n        (including malformed input, which the validators reject separately).\n    \"\"\"\n    return bool(_INTERVAL_MS_RE.fullmatch(str(schedule).strip()))\n\n\ndef validate_schedule(schedule: str) -> None:\n    \"\"\"Raise ``ValueError`` when *schedule* is malformed.\n\n    Args:\n        schedule: Either a positive integer string (interval-ms) or a\n            simplified 5-field cron expression.\n\n    Raises:\n        ValueError: When the schedule does not match either accepted form.\n    \"\"\"\n    if not schedule or not isinstance(schedule, str):\n        raise ValueError(\"schedule must be a non-empty string\")\n\n    if _INTERVAL_MS_RE.fullmatch(schedule.strip()):\n        # 15 digits ≈ 31,000 years in milliseconds — anything longer is not a\n        # usable interval and would only feed int() conversion of huge strings.\n        if len(schedule.strip()) > 15:\n            raise ValueError(\"interval is too large; expected at most 15 digits of milliseconds\")\n        return  # valid interval\n\n    parts = schedule.strip().split()\n    if len(parts) != _CRON_PARTS:\n        raise ValueError(f\"schedule must be a positive integer (ms) or a 5-field cron string; got: {schedule!r}\")\n    for part, (low, high) in zip(parts, CRON_BOUNDS):\n        _validate_cron_field(part, low, high)\n\n\ndef validate_timezone_shape(tz: Optional[str]) -> None:\n    \"\"\"Raise ``ValueError`` when *tz* is not ``None`` or a non-empty string.\n","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/scheduled_research/models.py#L108-L144","documentation":"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.","triggerScenarios":"create_scheduled_run(schedule=None), schedule='' , schedule=3600000 (int instead of '3600000'), or an unset JSON field deserialized to None and forwarded directly.","commonSituations":"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.","solutions":["Ensure schedule is a non-empty string before calling, e.g. '3600000' or '0 9 * * 1-5'.","Convert numeric intervals to strings: str(int(timedelta(minutes=30).total_seconds()*1000)).","Default missing config fields to a concrete schedule rather than forwarding None."],"exampleFix":"# before\ncreate_scheduled_run(schedule=None, ...)\n# after\ncreate_scheduled_run(schedule='0 9 * * 1-5', ...)","handlingStrategy":"type-guard","validationCode":"if not isinstance(schedule, str) or not schedule.strip():\n    schedule = '0 9 * * 1-5'  # sane default\ncreate_scheduled_run(schedule=schedule, ...)","typeGuard":"def is_schedule_str(s) -> bool:\n    return isinstance(s, str) and bool(s.strip())","tryCatchPattern":"try:\n    create_scheduled_run(schedule=schedule, ...)\nexcept ValueError as e:\n    if 'non-empty string' in str(e):\n        return bad_request('schedule is required')\n    raise","preventionTips":["Omit or null the field rather than sending ''.","Convert numeric intervals to str(ms) before calling."],"tags":["python","cron","validation","scheduler","type-error"],"backgroundTag":"cron-expression-invalid","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}