locustio/locust · error · Exception

TaskSet.run() is a method used internally by Locust, and you

Error message

TaskSet.run() is a method used internally by Locust, and you must not override it or annotate it with transitions

What it means

TaskSet.run() is Locust's internal execution loop. Overriding it — or annotating a method named 'run' with @transition — would corrupt the MarkovTaskSet machinery, so validate_task_name raises immediately when a decorated function is named 'run'.

Source

Thrown at locust/user/markov_taskset.py:253

def validate_task_name(decorated_func):
    """
    Validates that certain method names aren't used as Markov tasks.

    This function checks for special method names that shouldn't be used as Markov tasks:
    - "on_stop" and "on_start": Using these as Markov tasks will cause them to be called
      both as tasks AND on stop/start, which is usually not what the user intended.
    - "run": This method is used internally by Locust and must not be overridden or
      annotated with transitions.

    :param decorated_func: The function to validate
    :raises Exception: If the function name is "run"
    """
    if decorated_func.__name__ in ["on_stop", "on_start"]:
        logging.warning(
            "You have tagged your on_stop/start function with @transition. This will make the method get called both as a step AND on stop/start."
        )  # this is usually not what the user intended
    if decorated_func.__name__ == "run":
        raise Exception(
            "TaskSet.run() is a method used internally by Locust, and you must not override it or annotate it with transitions"
        )


def validate_markov_chain(tasks: list, class_dict: dict, classname: str):
    """
    Runs all validation functions on a Markov chain.

    :param tasks: List of Markov tasks to validate
    :param class_dict: Dictionary containing class attributes and methods
    :param classname: Name of the class being validated (for error/warning messages)
    :raises: Various exceptions if validation fails
    """
    validate_has_markov_tasks(tasks, classname)
    validate_transitions(tasks, class_dict, classname)
    validate_no_unreachable_tasks(tasks, class_dict, classname)
    for task in tasks:
        validate_task_name(task)

View on GitHub (pinned to f391a716e1)

Solutions

  1. Rename the method to something else, e.g. 'run_job', and update transitions accordingly
  2. Move any custom loop logic out of run() — let Locust's TaskSet.run drive the chain
  3. If you need lifecycle behavior, use on_start/on_stop instead of overriding run

Example fix

// before
@task
@transition(MyUser.next)
def run(self): ...
// after
@task
@transition(MyUser.next)
def run_job(self): ...
Defensive patterns

Strategy: validation

Validate before calling

assert not any(name == "run" for name in vars(MyUser)), "'run' is reserved in MarkovTaskSet"

Try / catch

try:
    user = MyUser(env)
except Exception as e:
    if "must not override" in str(e):
        logging.error("Rename the 'run' method")
    raise

Prevention

When it happens

Trigger: Defining 'def run(self):' decorated with @task/@transition inside a MarkovTaskSet; porting a TaskSet that overrode run() to MarkovTaskSet.

Common situations: Naming a task 'run' because the domain action is called that (e.g. 'run job'); copy-pasted legacy TaskSet code that overrode run for custom looping.

Related errors


AI-assisted analysis of locustio/locust@f391a716e1 (2026-08-29). Data as JSON: /api/errors/6c0dc1bceb5e2b87. Report an issue: GitHub.