odysseus-dev/odysseus · warning · HTTPException

Cron expression is required for cron schedule

Error message

Cron expression is required for cron schedule

What it means

Create-task validation: for trigger_type 'schedule' with schedule == 'cron', a cron_expression must be supplied. Without it the scheduler cannot compute next_run, so the request is rejected with HTTP 400 before the croniter validity check.

Source

Thrown at routes/task_routes.py:467

        return target.id

    @router.post("")
    async def create_task(request: Request, req: TaskCreate):
        user = _owner(request)

        # Validate
        if req.task_type in ("llm", "research") and not req.prompt:
            raise HTTPException(400, "Prompt is required for LLM/research tasks")
        if req.task_type == "action" and not req.action:
            raise HTTPException(400, "Action name is required for action tasks")
        # Block shell-executing action types for non-admins. action_run_local
        # uses subprocess.run(shell=True) and ssh_command / run_script run
        # arbitrary commands.
        _require_admin_for_task_action(user, req.task_type, req.action)
        if req.trigger_type == "schedule" and not req.schedule:
            raise HTTPException(400, "Schedule is required for schedule-triggered tasks")
        if req.trigger_type == "schedule" and req.schedule == "cron" and not req.cron_expression:
            raise HTTPException(400, "Cron expression is required for cron schedule")
        if req.trigger_type == "schedule" and req.schedule == "cron" and req.cron_expression:
            try:
                from croniter import croniter
                croniter(req.cron_expression)
            except Exception:
                raise HTTPException(400, "Invalid cron expression")
        if req.trigger_type == "event" and not req.trigger_event:
            raise HTTPException(400, "Event name is required for event-triggered tasks")
        if req.trigger_type == "event" and not req.trigger_count:
            raise HTTPException(400, "Trigger count is required for event-triggered tasks")

        # Auto-generate name
        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:

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Send a cron_expression (standard 5-field cron syntax accepted by croniter, e.g. '*/15 * * * *').
  2. Make cron_expression required client-side when schedule === 'cron'.
  3. If you wanted a fixed time instead, use the daily/time schedule mode rather than 'cron'.

Example fix

// before
{trigger_type: 'schedule', schedule: 'cron'}

// after
{trigger_type: 'schedule', schedule: 'cron', cron_expression: '0 9 * * 1-5'}
Defensive patterns

Strategy: validation

Validate before calling

function validateCronTask(p) {
  if (p.trigger_type === 'schedule' && p.schedule === 'cron' && !(p.cron_expression ?? '').trim())
    throw new ValidationError('Cron expression is required');
  return p;
}

Type guard

function isCronTask(p: unknown): p is {schedule: 'cron'; cron_expression: string} {
  return typeof p === 'object' && p !== null &&
    (p as any).schedule === 'cron' &&
    typeof (p as any).cron_expression === 'string' && (p as any).cron_expression.trim().length > 0;
}

Prevention

When it happens

Trigger: POST /api/tasks with {"trigger_type": "schedule", "schedule": "cron"} and cron_expression null/empty.

Common situations: Cron input hidden behind a toggle that was flipped after submit state was captured; advanced-cron UI mode not persisted; JSON import that strips the cron field.

Related errors


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