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 None

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Send ISO-8601 exactly: 'YYYY-MM-DDTHH:MM:SS' or with 'Z'/numeric offset, e.g. new Date(...).toISOString().
  2. Strip or convert textual timezone names ('UTC', 'GMT+2') to numeric offsets before submitting.
  3. On Python <3.11 servers, avoid exotic fractional-second widths; stick to seconds precision.
  4. 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

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


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/35f1786902169b2d. Report an issue: GitHub.