{"record":{"id":"1911844d2d134c1a","repo":"odysseus-dev/odysseus","slug":"schedule-is-required-for-schedule-triggered-tasks","errorCode":null,"errorMessage":"Schedule is required for schedule-triggered tasks","messagePattern":"Schedule is required for schedule-triggered tasks","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"routes/task_routes.py","lineNumber":465,"sourceCode":"        if not target:\n            raise HTTPException(404, \"Chained task not found\")\n        return target.id\n\n    @router.post(\"\")\n    async def create_task(request: Request, req: TaskCreate):\n        user = _owner(request)\n\n        # Validate\n        if req.task_type in (\"llm\", \"research\") and not req.prompt:\n            raise HTTPException(400, \"Prompt is required for LLM/research tasks\")\n        if req.task_type == \"action\" and not req.action:\n            raise HTTPException(400, \"Action name is required for action tasks\")\n        # Block shell-executing action types for non-admins. action_run_local\n        # uses subprocess.run(shell=True) and ssh_command / run_script run\n        # arbitrary commands.\n        _require_admin_for_task_action(user, req.task_type, req.action)\n        if req.trigger_type == \"schedule\" and not req.schedule:\n            raise HTTPException(400, \"Schedule is required for schedule-triggered tasks\")\n        if req.trigger_type == \"schedule\" and req.schedule == \"cron\" and not req.cron_expression:\n            raise HTTPException(400, \"Cron expression is required for cron schedule\")\n        if req.trigger_type == \"schedule\" and req.schedule == \"cron\" and req.cron_expression:\n            try:\n                from croniter import croniter\n                croniter(req.cron_expression)\n            except Exception:\n                raise HTTPException(400, \"Invalid cron expression\")\n        if req.trigger_type == \"event\" and not req.trigger_event:\n            raise HTTPException(400, \"Event name is required for event-triggered tasks\")\n        if req.trigger_type == \"event\" and not req.trigger_count:\n            raise HTTPException(400, \"Trigger count is required for event-triggered tasks\")\n\n        # Auto-generate name\n        name = req.name\n        if not name:\n            if req.task_type == \"action\":\n                from src.builtin_actions import BUILTIN_ACTION_INFO","sourceCodeStart":447,"sourceCodeEnd":483,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/task_routes.py#L447-L483","documentation":"Create-task validation: when trigger_type is 'schedule', the schedule sub-type must be provided (values such as 'once', daily-style schedules, or 'cron' — as consumed by compute_next_run). Missing schedule means next_run cannot be computed, so the request fails with HTTP 400.","triggerScenarios":"POST /api/tasks with {\"trigger_type\": \"schedule\"} and schedule null/empty; or a schedule string that is falsy after Pydantic defaults.","commonSituations":"Frontend schedule picker not bound to the payload; partial update JSON that sets trigger_type but omits schedule; schedule value sent as 'none'/None casing mismatch leaving it empty.","solutions":["Include a valid schedule value (e.g. 'once' with scheduled_date, or 'cron' with cron_expression) whenever trigger_type is 'schedule'.","Make the schedule field conditionally required in the client form when trigger_type === 'schedule'.","Cross-check the TaskCreate schema for the accepted schedule enum values and send one of those exactly."],"exampleFix":"// before\n{task_type: 'llm', prompt, trigger_type: 'schedule'}\n\n// after\n{task_type: 'llm', prompt, trigger_type: 'schedule', schedule: 'cron', cron_expression: '0 9 * * *'}","handlingStrategy":"validation","validationCode":"function validateSchedule(p) {\n  if (p.trigger_type === 'schedule' && !p.schedule)\n    throw new ValidationError('Schedule is required for schedule-triggered tasks');\n  return p;\n}","typeGuard":"function isScheduleTrigger(p: unknown): p is {trigger_type: 'schedule'; schedule: string} {\n  return typeof p === 'object' && p !== null &&\n    (p as any).trigger_type === 'schedule' &&\n    typeof (p as any).schedule === 'string' && (p as any).schedule.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Bind the schedule picker to the payload and mark it required when trigger_type is 'schedule'.","Keep a single shared enum of schedule values between client and server."],"tags":["tasks","validation","http-400","scheduling"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}