locustio/locust · error · NonMarkovTaskTransitionError

{classname}.{dest} cannot be used as a target for a transiti

Error message

{classname}.{dest} cannot be used as a target for a transition since it does not define any transitions of its own.Used as a transition from {task.__name__}.

What it means

A Markov transition target must itself be a Markov task, i.e. it must declare its own transitions. Pointing a transition at a plain @task without transitions breaks the chain (the walk could reach a dead end), so NonMarkovTaskTransitionError is raised.

Source

Thrown at locust/user/markov_taskset.py:180

    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
    """
    for task in tasks:
        for dest in task.transitions.keys():
            dest_task = class_dict.get(dest)
            if not dest_task:
                raise InvalidTransitionError(
                    f"Transition to {dest} from {task.__name__} is invalid since no such element exists on class {classname}"
                )
            if not is_markov_task(dest_task):
                raise NonMarkovTaskTransitionError(
                    f"{classname}.{dest} cannot be used as a target for a transition since it does not define any transitions of its own."
                    + f"Used as a transition from {task.__name__}."
                )


def validate_no_unreachable_tasks(tasks: list, class_dict: dict, classname: str):
    """
    Checks for and warns about unreachable Markov tasks in a MarkovTaskSet.

    This function uses depth-first search (DFS) starting from the first task to identify
    all reachable tasks. It then warns about any tasks that cannot be reached from the
    starting task through the defined transitions.

    :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 warning messages)
    :return: The original list of tasks
    """

View on GitHub (pinned to f391a716e1)

Solutions

  1. Give the target task at least one @transition (e.g. back to itself or to another task) so it qualifies as a Markov task
  2. If the state should be terminal, transition it to itself with a weight, or restructure the chain so it isn't a transition target
  3. Verify every @transition destination uses @task together with @transition/@transition_all

Example fix

// before
@task
def done(self):
    self.client.get("/done")
// after
@task
@transition(MyUser.done)  # loop back or point to another state
def done(self):
    self.client.get("/done")
Defensive patterns

Strategy: validation

Validate before calling

for t in MyUser.__dict__.values():
    for dest in getattr(t, "transitions", {}):
        assert getattr(MyUser.__dict__.get(dest), "transitions", None), f"{dest} is not a Markov task"

Try / catch

try:
    validate_markov_chain(tasks, dict(vars(MyUser)), MyUser.__name__)
except NonMarkovTaskTransitionError as e:
    logging.error(e)
    raise

Prevention

When it happens

Trigger: @transition(MyUser.final_step) where final_step is a @task with no @transition decorators of its own; adding a new terminal task and using it as a target from another task.

Common situations: Modeling a 'done/end' state as a plain task; forgetting that every reachable state in a Markov chain must define outgoing transitions; converting a plain TaskSet where last methods had no transitions.

Related errors


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