HKUDS/Vibe-Trading · error · ValueError

end_at must be RFC3339 text or epoch milliseconds

Error message

end_at must be RFC3339 text or epoch milliseconds

What it means

_parse_end_at rejects the end_at draft field when it is a boolean. Since bool is a subclass of int in Python, the explicit check exists to stop `true`/`false` JSON values from being silently treated as epoch 1/0 milliseconds.

Source

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

    host = sys.modules.get("api_server") or sys.modules.get("agent.api_server")
    executor = getattr(host, "_scheduled_research_executor", None) if host else None
    if executor is None:
        try:
            from src.api import scheduled_routes

            executor = scheduled_routes._scheduled_research_executor
        except Exception:
            executor = None
    if executor is not None:
        running = bool(executor.is_running)
    return {"enabled": enabled, "running": running, "executable": enabled and running}


def _parse_end_at(value: Any) -> int | None:
    if value in (None, ""):
        return None
    if isinstance(value, bool):
        raise ValueError("end_at must be RFC3339 text or epoch milliseconds")
    if isinstance(value, int):
        return value
    if not isinstance(value, str):
        raise ValueError("end_at must be RFC3339 text or epoch milliseconds")
    try:
        parsed = datetime.fromisoformat(value.strip().replace("Z", "+00:00"))
    except ValueError as exc:
        raise ValueError("end_at must be a valid RFC3339 timestamp") from exc
    if parsed.tzinfo is None:
        raise ValueError("end_at must include an explicit timezone offset")
    return int(parsed.timestamp() * 1000)


def _origin_target(session_id: str | None) -> tuple[str, str, str | None, str]:
    if not session_id:
        raise ValueError("origin delivery requires the originating session")
    host = sys.modules.get("api_server") or sys.modules.get("agent.api_server")
    service = (

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Send end_at as null/omitted for no end date, an RFC3339 string, or epoch milliseconds int
  2. Audit the client code that serializes the draft for boolean coercion
  3. If using YAML, quote values like "true" so they stay strings, or better, use real timestamps

Example fix

// before
{"end_at": true}
// after
{"end_at": null}  // or "2026-12-31T00:00:00Z" or 1798761600000
Defensive patterns

Strategy: validation

Validate before calling

assert draft.get("end_at") is None or isinstance(draft.get("end_at"), (str, int)) and not isinstance(draft.get("end_at"), bool)

Type guard

def valid_end_at_type(v) -> bool:
    return v is None or v == "" or (isinstance(v, (int, str)) and not isinstance(v, bool))

Try / catch

try:
    build_job_from_draft(draft)
except ValueError as e:
    if "RFC3339 text or epoch milliseconds" in str(e):
        fix_end_at_field()

Prevention

When it happens

Trigger: Passing end_at: true or end_at: false in the draft JSON (e.g. from a checkbox or feature flag leaking into the payload); any JSON boolean where a timestamp is expected.

Common situations: Frontend forms binding a toggle/checkbox to the end_at field; YAML/JSON config where an unquoted yes/no becomes a boolean; defaulting end_at to a flag instead of null.

Related errors


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