{"record":{"id":"acee04498d0660a8","repo":"odysseus-dev/odysseus","slug":"prompt-is-required-for-llm-research-tasks","errorCode":null,"errorMessage":"Prompt is required for LLM/research tasks","messagePattern":"Prompt is required for LLM/research tasks","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"routes/task_routes.py","lineNumber":457,"sourceCode":"        if not target_id:\n            return None\n        if current_task_id and target_id == current_task_id:\n            raise HTTPException(400, \"Task cannot chain to itself\")\n        q = db.query(ScheduledTask).filter(ScheduledTask.id == target_id)\n        if user:\n            q = q.filter(ScheduledTask.owner == user)\n        target = q.first()\n        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\")","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/task_routes.py#L439-L475","documentation":"Create-task validation: task_type 'llm' or 'research' requires a non-empty prompt field. These types execute an LLM prompt, so a blank prompt is rejected with HTTP 400 before any scheduling logic runs.","triggerScenarios":"POST /api/tasks with {\"task_type\": \"llm\"} or {\"task_type\": \"research\"} and prompt null/empty/whitespace.","commonSituations":"Frontend sends the form before the user types a prompt; prompt field name mismatch (e.g. 'text' vs 'prompt'); API client defaulting prompt to empty string; importing tasks from JSON where the prompt key was dropped.","solutions":["Include a non-empty prompt in the request body for llm/research tasks.","Add client-side required-field validation on the prompt input before submit.","Check for field-name typos in the payload against the TaskCreate schema (it must be 'prompt')."],"exampleFix":"// before\nawait api.createTask({task_type: 'llm', trigger_type: 'event', trigger_event: 'x'});\n\n// after\nconst prompt = promptInput.value.trim();\nif (!prompt) return showError('Prompt is required');\nawait api.createTask({task_type: 'llm', prompt, trigger_type: 'event', trigger_event: 'x'});","handlingStrategy":"validation","validationCode":"function validateTaskCreate(p) {\n  if ((p.task_type === 'llm' || p.task_type === 'research') && !(p.prompt ?? '').trim())\n    throw new ValidationError('Prompt is required for LLM/research tasks');\n  return p;\n}","typeGuard":"function isLlmLikeTask(t: unknown): t is {task_type: 'llm'|'research'} {\n  return typeof t === 'object' && t !== null &&\n    ['llm','research'].includes((t as any).task_type);\n}","tryCatchPattern":null,"preventionTips":["Make prompt a required input in the form for llm/research types (disable submit while empty).","Mirror the server's conditional-required rules in a shared schema (zod/pydantic export)."],"tags":["tasks","validation","http-400","llm"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}