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
- Set end_at to a future timestamp or omit it for no end
- Sync clocks / use server time when computing end_at from relative offsets
- 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
- Compute end_at at submit time, not at draft-creation time
- Regenerate end_at on retries; keep client clocks NTP-synced
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
- end_at must be RFC3339 text or epoch milliseconds
- the first scheduled run occurs after end_at
- timezone {tz!r} is not a recognized IANA timezone key
- unknown delivery status {raw_status!r}
- 'failure_kind' must be 'dispatch', 'schedule', or null
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/47e9f8b83af7848b.
Report an issue: GitHub.