odysseus-dev/odysseus · warning · HTTPException
Invalid scheduled_date format
Error message
Invalid scheduled_date format
What it means
Create-task validation: for schedule 'once', the scheduled_date string must be ISO-8601 parseable. The route does datetime.fromisoformat after replacing a trailing 'Z' with '+00:00', and catches only ValueError — any other parse failure type propagates to the generic 500 handler. A ValueError yields HTTP 400 'Invalid scheduled_date format'. Note the parsed value is then stripped of tzinfo, so the server treats it as local/naive time.
Source
Thrown at routes/task_routes.py:498
name = req.name
if not name:
if req.task_type == "action":
from src.builtin_actions import BUILTIN_ACTION_INFO
name = BUILTIN_ACTION_INFO.get(req.action, req.action or "Action Task")
elif req.prompt:
name = await _generate_task_name(req.prompt, owner=user)
else:
name = "Untitled Task"
# Compute next_run for schedule-triggered tasks
next_run = None
sched_date = None
if req.trigger_type == "schedule":
if req.schedule == "once" and req.scheduled_date:
try:
sched_date = datetime.fromisoformat(req.scheduled_date.replace("Z", "+00:00")).replace(tzinfo=None)
except ValueError:
raise HTTPException(400, "Invalid scheduled_date format")
next_run = compute_next_run(
req.schedule, req.scheduled_time,
req.scheduled_day, sched_date,
cron_expression=req.cron_expression,
)
# Generate webhook token if needed
webhook_token = None
if req.trigger_type == "webhook":
webhook_token = secrets.token_urlsafe(32)
task_id = str(uuid.uuid4())
db = SessionLocal()
try:
then_task_id = _validate_then_task_id(db, req.then_task_id, user)
notifications_enabled = (
False if req.task_type == "action" and req.notifications_enabled is None
else bool(req.notifications_enabled) if req.notifications_enabled is not NoneView on GitHub (pinned to f9235ebbf1)
Solutions
- Send ISO-8601 exactly: 'YYYY-MM-DDTHH:MM:SS' or with 'Z'/numeric offset, e.g. new Date(...).toISOString().
- Strip or convert textual timezone names ('UTC', 'GMT+2') to numeric offsets before submitting.
- On Python <3.11 servers, avoid exotic fractional-second widths; stick to seconds precision.
- Remember tzinfo is dropped server-side: convert to the server's local time before sending if the exact wall-clock moment matters.
Example fix
// before
{scheduled_date: new Date().toLocaleString()} // '8/14/2026, 10:00 AM'
// after
{scheduled_date: new Date().toISOString()} // '2026-08-14T10:00:00.000Z' Defensive patterns
Strategy: validation
Validate before calling
function isoDate(d) {
if (!(d instanceof Date) || isNaN(d)) throw new ValidationError('Invalid scheduled_date');
return d.toISOString(); // always 'YYYY-MM-DDTHH:MM:SS.sssZ'
} Type guard
function isIsoDateTime(s: unknown): s is string {
return typeof s === 'string' &&
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/.test(s);
} Prevention
- Always serialize dates with toISOString(); never toLocaleString().
- Use a date/time picker component, not free text.
- Remember the server drops tzinfo: convert to server-local time first when exactness matters.
When it happens
Trigger: POST /api/tasks with schedule 'once' and scheduled_date like '2026-31-12', 'tomorrow', '2026-08-14 10:00 UTC' (bad separator), or a date-only value that fromisoformat rejects in older Python (<3.11 rejects '2026-08-14Z' variants even after Z-replacement if format is odd).
Common situations: JS client formatting with toLocaleString instead of toISOString; user-typed date in a free-text field; timezone suffixes other than 'Z' ('+05:30' works, but 'UTC'/'GMT' text does not); Python <3.11 server where fromisoformat is strict about 'YYYY-MM-DD HH:MM:SS' vs 'T' separators and fractional widths.
Related errors
- Task cannot chain to itself
- Prompt is required for LLM/research tasks
- Action name is required for action tasks
- Schedule is required for schedule-triggered tasks
- Cron expression is required for cron schedule
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/35f1786902169b2d.
Report an issue: GitHub.