HKUDS/Vibe-Trading · error · ValueError

the first scheduled run occurs after end_at

Error message

the first scheduled run occurs after end_at

What it means

The job's first scheduled run (next_run_at, computed from the schedule expression/timezone, or 'now' for interval schedules) falls after end_at, so the job could never fire before expiring.

Source

Thrown at agent/src/scheduled_research/service.py:167

    else:
        raise ValueError("source.kind must be 'prompt' or 'playbook'")

    expression = str(schedule_spec.get("expression") or "").strip()
    timezone = schedule_spec.get("timezone")
    validate_schedule(expression)
    if is_interval_schedule(expression):
        validate_timezone_shape(timezone)
    else:
        validate_timezone(timezone)
    next_run_at = now
    if timezone is not None and not is_interval_schedule(expression):
        next_run_at = next_due(expression, now, timezone)

    end_at = _parse_end_at(draft.get("end_at"))
    if end_at is not None and end_at <= now:
        raise ValueError("end_at must be in the future")
    if end_at is not None and next_run_at > end_at:
        raise ValueError("the first scheduled run occurs after end_at")

    mode = str(delivery_spec.get("mode") or "in_app")
    delivery_channel = delivery_target = target_ref = target_label = None
    if mode == "configured":
        target_ref = str(delivery_spec.get("target_ref") or "").strip()
        if not target_ref:
            raise ValueError("delivery.target_ref is required for configured delivery")
        target = resolve_delivery_target(target_ref)
        delivery_channel, delivery_target = target.channel, target.target
        target_label = target.label
    elif mode == "origin":
        delivery_channel, delivery_target, target_ref, target_label = _origin_target(
            session_id
        )
    elif mode != "in_app":
        raise ValueError("delivery.mode must be 'in_app', 'origin', or 'configured'")

    return ScheduledResearchJob(

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Extend end_at beyond the first occurrence, or omit it
  2. Use a more frequent schedule expression so a run occurs before end_at
  3. Double-check timezone: compute next_due in the job's timezone to verify it precedes end_at

Example fix

// before
{"schedule": {"expression": "0 9 * * 1"}, "end_at": "<3 days from now>"}
// after
{"schedule": {"expression": "0 9 * * 1"}, "end_at": "<10 days from now>"}
Defensive patterns

Strategy: validation

Validate before calling

from scheduled_research.schedule import next_due, is_interval_schedule
nra = now_ms if is_interval_schedule(expr) else next_due(expr, now_ms, tz)
assert end_at is None or nra <= end_at, "first run after end_at"

Type guard

def schedule_fits_window(expr: str, tz, end_at: int | None, now: int) -> bool:
    return end_at is None or (now if is_interval_schedule(expr) else next_due(expr, now, tz)) <= end_at

Try / catch

try:
    propose_create(draft)
except ValueError as e:
    if "after end_at" in str(e):
        draft.pop("end_at", None); retry()  # or extend end_at

Prevention

When it happens

Trigger: A weekly cron with an end_at 3 days out; interval schedules where next_run_at defaults to now but end_at is in the past millisecond boundary; timezone shifts pushing the next due date beyond end_at.

Common situations: Users setting short end dates on infrequent schedules; timezone conversions moving next_due across the end boundary; test fixtures with tiny end_at windows.

Related errors


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