deepset-ai/haystack · error · PipelineMaxComponentRuns

Maximum run count {self._max_runs_per_component} reached for

Error message

Maximum run count {self._max_runs_per_component} reached for component '${component_name}'

What it means

PipelineBase._get_next_runnable_component raises PipelineMaxComponentRuns when a component is about to run again but has already been visited _max_runs_per_component times (default 100). This guards against infinite loops caused by cyclic pipeline graphs, e.g. looping agents or conditional branches that keep routing back to the same component.

Source

Thrown at haystack/core/pipeline/base.py:1468

        :param priority_queue: Priority queue of component names.
        :param component_visits: Current state of component visits.
        :returns: The next runnable component, the component name, and its priority
            or None if no component in the queue can run.
        :raises: PipelineMaxComponentRuns if the next runnable component has exceeded the maximum number of runs.
        """
        item = priority_queue.get()

        # If no component is runnable, return None
        if item is None:
            return None

        component_name = item[1]
        comp = self._get_component_with_graph_metadata_and_visits(component_name, component_visits[component_name])
        # Only raise the max run count error if the component is not blocked, since if it's blocked it means it
        # can't run anyway.
        if item[0] < ComponentPriority.BLOCKED and comp["visits"] >= self._max_runs_per_component:
            msg = f"Maximum run count {self._max_runs_per_component} reached for component '{component_name}'"
            raise PipelineMaxComponentRuns(msg)
        return ComponentPriority(item[0]), component_name, comp

    @staticmethod
    def _add_missing_input_defaults(
        component_inputs: dict[str, list[dict[str, Any]]], component_input_sockets: dict[str, InputSocket]
    ) -> dict[str, Any]:
        """
        Updates the inputs with the default values for the inputs that are missing

        :param component_inputs: Inputs for the component.
        :param component_input_sockets: Input sockets of the component.
        """
        for name, socket in component_input_sockets.items():
            if not socket.is_mandatory and name not in component_inputs:
                # NOTE: Variadic inputs expect a single default value in the function signature that matches the inner
                # type, for example Variadic[str] = "default". When executed inside a pipeline, we wrap this
                # default into a list, resulting in ["default"],  which is the intended behavior.
                #

View on GitHub (pinned to e318778c9b)

Solutions

  1. Inspect the loop condition: ensure the branch that exits the cycle can actually be taken (fix the router/agent stopping logic).
  2. Raise the limit if more iterations are legitimate: pipeline.max_runs_per_component = <higher number> before run().
  3. Remove unintended cycle edges by checking pipeline.connect() calls that create back-edges in the graph.

Example fix

// before
pipeline = Pipeline(max_runs_per_component=10)  # loop needs more iterations
// after
pipeline = Pipeline(max_runs_per_component=100)  # and verify the loop's exit condition
Defensive patterns

Strategy: validation

Validate before calling

# ensure the loop can exit and the limit fits the worst-case iterations
assert pipeline_has_exit_condition(router) , "router loop has no exit branch"
assert pipeline.max_runs_per_component >= expected_max_iterations

Type guard

def loop_terminates(router, exit_value) -> bool:
    return any(exit_value in cond for cond in router.output_slots())

Try / catch

try:
    result = pipeline.run(data)
except PipelineMaxComponentRuns as e:
    logger.error("Loop did not converge: %s", e)

Prevention

When it happens

Trigger: Running a pipeline with a cycle (e.g. a loop/back-edge from an evaluator back to a generator or agent) that never terminates; setting pipeline.max_runs_per_component to a low value while the loop legitimately needs more iterations.

Common situations: Agent/tool-loop pipelines where the agent keeps re-running the same component without converging; a conditional router whose condition is never satisfied so the loop runs forever; accidental self-connect of a component.

Related errors


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/e9e09bb1c063f819. Report an issue: GitHub.