odysseus-dev/odysseus · error · HTTPException

Action '{task.action}' requires admin privileges

Error message

Action '{task.action}' requires admin privileges

What it means

HTTP 403 from the unauthenticated webhook trigger when the task's (task_type, action) is classified admin-only by is_admin_only_task_action() but the task owner no longer holds admin privileges (owner_has_admin_task_privileges). As a safety measure the endpoint sets the task to 'paused' and clears next_run before raising, so the privileged action cannot keep firing.

Source

Thrown at routes/task_routes.py:1064

    async def webhook_trigger(task_id: str, token: str):
        """Unauthenticated endpoint — the token IS the auth."""
        db = SessionLocal()
        try:
            task = db.query(ScheduledTask).filter(
                ScheduledTask.id == task_id,
                ScheduledTask.webhook_token == token,
                ScheduledTask.status == "active",
            ).first()
            if not task:
                raise HTTPException(404, "Not found")
            if (
                is_admin_only_task_action(task.task_type, task.action)
                and not owner_has_admin_task_privileges(task.owner)
            ):
                task.status = "paused"
                task.next_run = None
                db.commit()
                raise HTTPException(403, f"Action '{task.action}' requires admin privileges")
        finally:
            db.close()
        started = await task_scheduler.run_task_now(task_id)
        if not started:
            raise HTTPException(409, "Task is already running")
        return {"ok": True, "message": "Task triggered via webhook"}

    @router.post("/{task_id}/webhook-regenerate")
    async def regenerate_webhook(request: Request, task_id: str):
        user = _owner(request)
        db = SessionLocal()
        try:
            task = db.query(ScheduledTask).filter(ScheduledTask.id == task_id).first()
            if not task:
                raise HTTPException(404, "Task not found")
            if user and task.owner != user:
                raise HTTPException(403, "Access denied")
            task.webhook_token = secrets.token_urlsafe(32)

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Grant the task owner admin privileges, then manually resume the task (it was paused by this guard)
  2. Change the task's action to a non-admin equivalent so it no longer trips is_admin_only_task_action
  3. Delete and recreate the task under an account that holds admin privileges

Example fix

# before: task paused + 403 on webhook
# after: restore privileges and resume
UPDATE users SET is_admin=1 WHERE username='<task.owner>';
# then PUT /api/tasks/{id} with {"status":"active"}
Defensive patterns

Strategy: validation

Validate before calling

const task = await getTask(taskId);
if (isAdminOnlyAction(task) && !currentUserIsAdmin(task.owner)) {
  showWarning('This task requires an admin owner; it will be paused on trigger.');
}

Try / catch

if (resp.status === 403) { await resumeTaskAfterPrivilegeFix(taskId); alertOwner(task.owner); }

Prevention

When it happens

Trigger: POST /api/tasks/{task_id}/webhook/{token} for a task whose action is in the admin-only set (e.g. a shell/system action) after the owner's admin rights were revoked or the admin flag was never set for that user.

Common situations: An admin created a privileged scheduled task, later stepped down or the account flag changed; tasks migrated from a single-admin install into a multi-user deployment where the owner is a normal user.

Related errors


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