HKUDS/Vibe-Trading · error · ValueError
end_at must include an explicit timezone offset
Error message
end_at must include an explicit timezone offset
What it means
The end_at timestamp parsed successfully but carried no timezone information (no 'Z' suffix or ±HH:MM offset). Because the value is converted to epoch milliseconds, an implicit local-time interpretation would be ambiguous, so an explicit offset is required.
Source
Thrown at agent/src/scheduled_research/service.py:91
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 = (
host._get_session_service()
if host and hasattr(host, "_get_session_service")
else None
)
session = service.get_session(session_id) if service else None
config = getattr(session, "config", None) or {}
channel = config.get("channel")
target = config.get("channel_chat_id")
if not isinstance(channel, str) or not isinstance(target, str):
raise ValueError("the originating session is not an IM conversation")View on GitHub (pinned to 80ffdda44c)
Solutions
- Append the offset: use datetime.now(timezone.utc).isoformat() or add 'Z'
- Configure serializers to always include the zone
- Or pass epoch milliseconds, which are inherently UTC
Example fix
# before datetime(2026,12,31).isoformat() # naive # after datetime(2026,12,31,tzinfo=timezone.utc).isoformat() # '2026-12-31T00:00:00+00:00'
Defensive patterns
Strategy: validation
Validate before calling
from datetime import datetime
def has_tz(s: str) -> bool:
try:
return datetime.fromisoformat(s.strip().replace("Z", "+00:00")).tzinfo is not None
except ValueError:
return False
assert has_tz(draft["end_at"]) Type guard
def is_timezone_aware_rfc3339(s: str) -> bool:
try:
return datetime.fromisoformat(s.strip().replace("Z", "+00:00")).tzinfo is not None
except ValueError:
return False Try / catch
try:
build_job_from_draft(draft)
except ValueError as e:
if "explicit timezone" in str(e):
draft["end_at"] += "+00:00" # or re-format as UTC Prevention
- Always produce timestamps with datetime.now(timezone.utc) or ISO strings ending in Z
- Treat naive datetimes as a bug in client serialization
When it happens
Trigger: Passing '2026-12-31T00:00:00' (naive ISO string) without offset; clients stripping the timezone during serialization; date libraries defaulting to naive datetimes.
Common situations: Python datetime.isoformat() on a naive datetime; JavaScript date formatting that drops the zone; users in non-UTC zones getting silently shifted times, hence the strict rule.
Related errors
- timezone {tz!r} is not a recognized IANA timezone key
- end_at must be a valid RFC3339 timestamp
- {model}: generated_at must be timezone-aware (e.g. datetime.
- timezone must be a non-empty IANA timezone key or null
- unknown delivery status {raw_status!r}
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/516a8f5075e0c40c.
Report an issue: GitHub.