odysseus-dev/odysseus · warning · HTTPException

Action name is required for action tasks

Error message

Action name is required for action tasks

What it means

Create-task validation: task_type 'action' requires a non-empty action name naming a built-in or registered action. Rejected with HTTP 400 before the admin-privilege check for admin-only actions runs.

Source

Thrown at routes/task_routes.py:459

        if current_task_id and target_id == current_task_id:
            raise HTTPException(400, "Task cannot chain to itself")
        q = db.query(ScheduledTask).filter(ScheduledTask.id == target_id)
        if user:
            q = q.filter(ScheduledTask.owner == user)
        target = q.first()
        if not target:
            raise HTTPException(404, "Chained task not found")
        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")

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Set action to a valid action name (see BUILTIN_ACTION_INFO keys served by the actions listing endpoint).
  2. Make the client action selector required and disable submit until a real action is chosen.
  3. If the action name comes from a config, verify it matches a key in src.builtin_actions.BUILTIN_ACTION_INFO.

Example fix

// before
await api.createTask({task_type: 'action'}); // no action

// after
if (!actionSelect.value) return showError('Pick an action');
await api.createTask({task_type: 'action', action: actionSelect.value});
Defensive patterns

Strategy: validation

Validate before calling

function validateActionTask(p) {
  if (p.task_type === 'action' && !(p.action ?? '').trim())
    throw new ValidationError('Action name is required');
  return p;
}

Type guard

function hasActionName(p: unknown): p is {task_type: 'action'; action: string} {
  return typeof p === 'object' && p !== null &&
    (p as any).task_type === 'action' &&
    typeof (p as any).action === 'string' && (p as any).action.length > 0;
}

Prevention

When it happens

Trigger: POST /api/tasks with {"task_type": "action"} and action null/empty/whitespace.

Common situations: Action dropdown left on its placeholder option whose value is ''; action key renamed between versions so the client sends the old empty default; JSON import missing the action field.

Related errors


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