HKUDS/Vibe-Trading · error · ValueError

end_at must be in the future

Error message

end_at must be in the future

What it means

The parsed end_at (epoch ms) is not strictly in the future relative to the job creation time. Jobs that would already be expired at creation are rejected as nonsensical.

Source

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

        prompt = playbook.render(variables)
        config["playbook"] = playbook_slug
    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'")

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Set end_at to a future timestamp or omit it for no end
  2. Sync clocks / use server time when computing end_at from relative offsets
  3. On retry, regenerate end_at rather than resending the original value

Example fix

// before
{"end_at": 1600000000000}
// after
{"end_at": 3900000000000}  // future, or omit
Defensive patterns

Strategy: validation

Validate before calling

now_ms = int(time.time() * 1000)
end = draft.get("end_at")
assert end is None or (isinstance(end, int) and not isinstance(end, bool) and end > now_ms)

Type guard

def end_at_in_future(v) -> bool:
    if v is None or v == "":
        return True
    if isinstance(v, bool) or not isinstance(v, int):
        return False
    return v > int(time.time() * 1000)

Try / catch

try:
    propose_create(draft)
except ValueError as e:
    if "end_at must be in the future" in str(e):
        draft.pop("end_at", None); retry()

Prevention

When it happens

Trigger: Passing an end_at in the past (epoch ms or dated string); clock skew between client and server; long-held drafts submitted after their intended end date.

Common situations: Client clocks ahead/behind server; retrying an old draft payload; DST or year typos (e.g. 2025 vs 2026) making the date past.

Related errors


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