locustio/locust · error · NoMarkovTasksError

No Markov tasks defined in class {classname}. Use the @trans

Error message

No Markov tasks defined in class {classname}. Use the @transition(s) decorators to define some.

What it means

A MarkovTaskSet subclass must define at least one @task whose transitions are declared via the @transition/@transition_all decorators. validate_has_markov_tasks raises NoMarkovTasksError when the class defines no Markov tasks, because the Markov chain would have no states to traverse.

Source

Thrown at locust/user/markov_taskset.py:153


def to_weighted_list(transitions: dict):
    return [name for name in transitions.keys() for _ in range(transitions[name])]


def validate_has_markov_tasks(tasks: list, classname: str):
    """
    Validates that a MarkovTaskSet has at least one Markov task.

    This function is used internally during MarkovTaskSet validation to ensure
    that the class has at least one method decorated with @transition or @transitions.

    :param tasks: List of tasks to validate
    :param classname: Name of the class being validated (for error messages)
    :raises NoMarkovTasksError: If no Markov tasks are found
    """
    if not tasks:
        raise NoMarkovTasksError(
            f"No Markov tasks defined in class {classname}. Use the @transition(s) decorators to define some."
        )


def validate_transitions(tasks: list, class_dict: dict, classname: str):
    """
    Validates that all transitions in Markov tasks point to existing Markov tasks.

    This function checks two conditions for each transition:
    1. The target task exists in the class
    2. The target task is also a Markov task (has transitions defined)

    :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 messages)
    :raises InvalidTransitionError: If a transition points to a non-existent task
    :raises NonMarkovTaskTransitionError: If a transition points to a task that isn't a Markov task
    """

View on GitHub (pinned to f391a716e1)

Solutions

  1. Add @task decorated methods and mark transitions between them with @transition(MyUser.task_b, weight=...)
  2. If no Markov behavior is needed, inherit from TaskSet/SequentialTaskSet instead of MarkovTaskSet
  3. Copy a minimal MarkovTaskSet example and add your own states

Example fix

// before
class MyUser(MarkovTaskSet):
    def browse(self):
        self.client.get("/")
// after
class MyUser(MarkovTaskSet):
    @task
    @transition(MyUser.checkout)
    def browse(self):
        self.client.get("/")

    @task
    def checkout(self):
        self.client.post("/checkout")
Defensive patterns

Strategy: validation

Validate before calling

assert any(hasattr(m, "transitions") for m in vars(MyUser).values()), "MarkovTaskSet needs @transition-decorated tasks"

Try / catch

try:
    user = MyUser(env)
except NoMarkovTasksError as e:
    logging.error(f"Fix class definition: {e}")
    raise

Prevention

When it happens

Trigger: Defining 'class MyUser(MarkovTaskSet)' with no @task/@transition methods at all, or only plain methods with no transition decorators, then instantiating or validating the class.

Common situations: Migrating a regular TaskSet to MarkovTaskSet and forgetting the @transition decorators; leaving a placeholder class empty; typos such as using @task without @transition and assuming tasks alone are enough.

Related errors


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