{"record":{"id":"35f1786902169b2d","repo":"odysseus-dev/odysseus","slug":"invalid-scheduled-date-format","errorCode":null,"errorMessage":"Invalid scheduled_date format","messagePattern":"Invalid scheduled_date format","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"routes/task_routes.py","lineNumber":498,"sourceCode":"        name = req.name\n        if not name:\n            if req.task_type == \"action\":\n                from src.builtin_actions import BUILTIN_ACTION_INFO\n                name = BUILTIN_ACTION_INFO.get(req.action, req.action or \"Action Task\")\n            elif req.prompt:\n                name = await _generate_task_name(req.prompt, owner=user)\n            else:\n                name = \"Untitled Task\"\n\n        # Compute next_run for schedule-triggered tasks\n        next_run = None\n        sched_date = None\n        if req.trigger_type == \"schedule\":\n            if req.schedule == \"once\" and req.scheduled_date:\n                try:\n                    sched_date = datetime.fromisoformat(req.scheduled_date.replace(\"Z\", \"+00:00\")).replace(tzinfo=None)\n                except ValueError:\n                    raise HTTPException(400, \"Invalid scheduled_date format\")\n            next_run = compute_next_run(\n                req.schedule, req.scheduled_time,\n                req.scheduled_day, sched_date,\n                cron_expression=req.cron_expression,\n            )\n\n        # Generate webhook token if needed\n        webhook_token = None\n        if req.trigger_type == \"webhook\":\n            webhook_token = secrets.token_urlsafe(32)\n\n        task_id = str(uuid.uuid4())\n        db = SessionLocal()\n        try:\n            then_task_id = _validate_then_task_id(db, req.then_task_id, user)\n            notifications_enabled = (\n                False if req.task_type == \"action\" and req.notifications_enabled is None\n                else bool(req.notifications_enabled) if req.notifications_enabled is not None","sourceCodeStart":480,"sourceCodeEnd":516,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/task_routes.py#L480-L516","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\n{scheduled_date: new Date().toLocaleString()} // '8/14/2026, 10:00 AM'\n\n// after\n{scheduled_date: new Date().toISOString()} // '2026-08-14T10:00:00.000Z'","handlingStrategy":"validation","validationCode":"function isoDate(d) {\n  if (!(d instanceof Date) || isNaN(d)) throw new ValidationError('Invalid scheduled_date');\n  return d.toISOString(); // always 'YYYY-MM-DDTHH:MM:SS.sssZ'\n}","typeGuard":"function isIsoDateTime(s: unknown): s is string {\n  return typeof s === 'string' &&\n    /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})?$/.test(s);\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["tasks","validation","http-400","datetime","iso-8601"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}