HKUDS/Vibe-Trading · error · TypeError
'end_at' must be an integer (epoch ms) or null
Error message
'end_at' must be an integer (epoch ms) or null
What it means
Raised by Job from_dict (agent/src/scheduled_research/models.py:484) when the optional 'end_at' field is present, non-null, and not an int — with bools explicitly rejected (isinstance(end_at, bool) check) even though bool subclasses int. It is the job's expiry as epoch milliseconds.
Source
Thrown at agent/src/scheduled_research/models.py:484
logger.warning(
"scheduled research job %s has an unusable timezone %r; "
"evaluating its schedule in UTC",
job_id,
raw_tz,
)
status = JobStatus(data["status"])
title = data.get("title", "")
source_type = data.get("source_type", "prompt")
playbook_slug = data.get("playbook_slug")
end_at = data.get("end_at")
if not isinstance(title, str):
raise TypeError("'title' must be a string")
if source_type not in {"prompt", "playbook"}:
raise ValueError("'source_type' must be 'prompt' or 'playbook'")
if playbook_slug is not None and not isinstance(playbook_slug, str):
raise TypeError("'playbook_slug' must be a string or null")
if end_at is not None and (isinstance(end_at, bool) or not isinstance(end_at, int)):
raise TypeError("'end_at' must be an integer (epoch ms) or null")
raw_config = data.get("config")
config: Dict[str, Any] = raw_config if isinstance(raw_config, dict) else {}
delivery_channel = data.get("delivery_channel")
delivery_target = data.get("delivery_target")
delivery_target_ref = data.get("delivery_target_ref")
delivery_target_label = data.get("delivery_target_label")
for name, value in (
("delivery_channel", delivery_channel),
("delivery_target", delivery_target),
("delivery_target_ref", delivery_target_ref),
("delivery_target_label", delivery_target_label),
):
if value is not None and not isinstance(value, str):
raise TypeError(f"'{name}' must be a string or null")
return cls(
id=job_id,
prompt=prompt,
schedule=schedule,View on GitHub (pinned to 80ffdda44c)
Solutions
- Write int epoch ms: int(dt.timestamp() * 1000)
- Never store booleans in timestamp fields; use null for 'no end'
- Convert numeric strings before persisting
Example fix
// before record["end_at"] = end_date.isoformat() // after record["end_at"] = int(end_date.timestamp() * 1000)
Defensive patterns
Strategy: validation
Validate before calling
def end_at_ok(v):
return v is None or (isinstance(v, int) and not isinstance(v, bool)) Type guard
from typing import Any, Optional
def safe_end_at(v: Any) -> Optional[int]:
if v is None or isinstance(v, bool) or not isinstance(v, int):
return None
return v Try / catch
try:
job = Job.from_dict(record)
except TypeError as exc:
if "end_at" in str(exc):
record = dict(record); record["end_at"] = None
job = Job.from_dict(record)
else:
raise Prevention
- Use the epoch-ms helper for end_at too
- Store null for open-ended jobs
- Keep booleans out of timestamp slots
When it happens
Trigger: {"end_at": "1767225600000"}, {"end_at": 1767225600.5}, {"end_at": true}, or an ISO datetime string.
Common situations: Epoch seconds vs ms; ISO strings from default JSON encoders; a boolean 'ended' flag accidentally placed in the end_at slot.
Related errors
- 'delivery.updated_at' must be an integer (epoch ms) or null
- 'next_run_at' and 'created_at' must be integers (epoch ms)
- 'last_run_at' must be an integer (epoch ms) or null
- 'delivery' must be an object or null
- unknown delivery status {raw_status!r}
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/5107243b91b35e0a.
Report an issue: GitHub.