HKUDS/Vibe-Trading · error · ValueError
schedule must be a positive integer (ms) or a 5-field cron s
Error message
schedule must be a positive integer (ms) or a 5-field cron string; got: {schedule!r} What it means
After the interval form is ruled out, the schedule must split into exactly 5 whitespace-separated cron fields (minute hour day-of-month month day-of-week). Any other field count — 6-field Quartz strings with seconds, single-word shorthands like '@daily', or truncated strings — is rejected.
Source
Thrown at agent/src/scheduled_research/models.py:137
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.
This is the persistence-level check: it deliberately does NOT resolve the
key, because resolvability depends on the host's timezone database. A
store written where a key resolved must keep loading and persisting on a
host where it does not; the executor surfaces the unresolvable key as a
per-job schedule failure instead.
"""
if tz is None:
return
if not isinstance(tz, str) or not tz.strip():
raise ValueError("timezone must be a non-empty IANA timezone key or null")
View on GitHub (pinned to 80ffdda44c)
Solutions
- Rewrite as exactly 5 fields: drop the leading seconds field, translate @daily to '0 0 * * *', @hourly to '0 * * * *'.
- For fixed-rate schedules, use the millisecond interval form instead of a 6-field expression.
- Validate user-supplied schedules with validate_schedule at ingestion time.
Example fix
# before
validate_schedule('0 */2 * * * *')
# after
validate_schedule('*/120 * * * *') # or '7200000' ms Defensive patterns
Strategy: validation
Validate before calling
parts = schedule.split()
if len(parts) == 6:
schedule = ' '.join(parts[1:]) # drop Quartz seconds field
SHORTHAND = {'@hourly': '0 * * * *', '@daily': '0 0 * * *', '@weekly': '0 0 * * 0'}
schedule = SHORTHAND.get(schedule, schedule)
validate_schedule(schedule) Type guard
def is_five_field_cron(s: str) -> bool:
return isinstance(s, str) and len(s.split()) == 5 Try / catch
try:
validate_schedule(schedule)
except ValueError as e:
return bad_request(f'invalid schedule: {e}') Prevention
- Expect exactly 5 fields; drop leading seconds from Quartz strings.
- Translate @-shorthands before submission.
- Use ms-interval strings for fixed rates.
When it happens
Trigger: validate_schedule('* * * * * *') (6 fields), validate_schedule('@hourly'), validate_schedule('0 9 * *') (4 fields), or tabs/multiple spaces are fine but wrong field count still fails.
Common situations: Copying a Quartz/CloudWatch/EventBridge expression that includes a seconds field; using @-shorthands from Vixie cron; trailing truncation when a schedule string is cut off in config or logging.
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/1f3c9add6b02bdf8.
Report an issue: GitHub.