{"record":{"id":"f8f4ad6aaf67dc51","repo":"apache/superset","slug":"user-id-is-required-for-private-tasks","errorCode":null,"errorMessage":"user_id is required for private tasks","messagePattern":"user_id is required for private tasks","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"superset/daos/tasks.py","lineNumber":137,"sourceCode":"        already checked for existing tasks. Business logic (create vs join)\n        is handled by SubmitTaskCommand.\n\n        :param task_type: Type of task to create\n        :param task_key: Task identifier (required)\n        :param scope: Task scope (private/shared/system), defaults to private\n        :param user_id: User ID creating the task\n        :param payload: Optional user-defined context data (dict)\n        :param properties: Optional framework-managed runtime state (e.g., timeout)\n        :param kwargs: Additional task attributes (e.g., task_name)\n        :returns: Created Task instance\n        \"\"\"\n        # Handle both TaskScope enum and string values\n        scope_value = scope.value if isinstance(scope, TaskScope) else scope\n        scope_enum = scope if isinstance(scope, TaskScope) else TaskScope(scope)\n\n        # Validate user_id is required for private tasks\n        if scope_enum == TaskScope.PRIVATE and user_id is None:\n            raise ValueError(\"user_id is required for private tasks\")\n\n        # Build dedup_key for active task\n        dedup_key = get_active_dedup_key(\n            scope=scope,\n            task_type=task_type,\n            task_key=task_key,\n            user_id=user_id,\n        )\n\n        # Note: properties is handled separately via update_properties()\n        task_data = {\n            \"task_type\": task_type,\n            \"task_key\": task_key,\n            \"scope\": scope_value,\n            \"status\": TaskStatus.PENDING.value,\n            \"dedup_key\": dedup_key,\n            **kwargs,\n        }","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/daos/tasks.py#L119-L155","documentation":"Raised by TaskDAO.create_task (superset/daos/tasks.py) when scope is TaskScope.PRIVATE but user_id is None. Private tasks are keyed and deduplicated per user (get_active_dedup_key includes user_id), so an anonymous private task cannot be deduplicated or scoped; the DAO enforces the invariant with a ValueError before building the dedup key. Scope accepts either the TaskScope enum or its string value and normalizes both, but the user_id requirement applies regardless.","triggerScenarios":"Calling TaskDAO.create_task(scope='PRIVATE', task_type=..., task_key=..., user_id=None); background/command code that starts private tasks without threading the requesting user through; defaulting user_id to None in a new integration instead of g.user.id.","commonSituations":"New notification/alert execution paths or custom schedulers adopting the tasks framework and forgetting user propagation; refactors that drop user_id from kwargs; calling task creation outside a request context where user_id must be passed explicitly rather than derived.","solutions":["Pass user_id=get_user_id() (or the owning user's id) whenever creating a PRIVATE task.","If the task is genuinely system-wide, use TaskScope.GLOBAL instead of PRIVATE.","Validate scope/user_id pairing at the API boundary so callers get a 4xx, not a ValueError from the DAO.","In celery contexts with no request, read the user from the trigger payload and pass it explicitly."],"exampleFix":"# before\nTaskDAO.create_task(scope=TaskScope.PRIVATE, task_type='alert', task_key=key)  # ValueError\n\n# after\nTaskDAO.create_task(\n    scope=TaskScope.PRIVATE,\n    task_type='alert',\n    task_key=key,\n    user_id=get_user_id(),  # required for PRIVATE scope\n)","handlingStrategy":"validation","validationCode":"from superset.daos.task import TaskScope  # adjust import path to actual enum home\nfrom superset.utils.core import get_user_id\n\ndef validate_task_scope(scope: TaskScope, user_id: int | None) -> list[str]:\n    errors = []\n    if scope == TaskScope.PRIVATE and user_id is None:\n        errors.append('user_id is required for private tasks')\n    return errors","typeGuard":"def is_valid_private_task(scope: TaskScope, user_id: int | None) -> TypeGuard[bool]:\n    return scope != TaskScope.PRIVATE or user_id is not None","tryCatchPattern":"try:\n    task = TaskDAO.create_task(scope=scope, task_type=task_type, task_key=task_key, user_id=user_id)\nexcept ValueError as err:\n    if 'private tasks' in str(err):\n        task = TaskDAO.create_task(scope=scope, task_type=task_type, task_key=task_key, user_id=get_user_id())","preventionTips":["Always pass the triggering user's id when creating PRIVATE tasks, including in celery contexts.","Enforce the scope/user_id pairing in API schemas so callers get 4xx instead of a DAO ValueError.","Use GLOBAL scope only for genuinely system-wide tasks."],"tags":["tasks","dao","scope","validation","user-context"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}