deepset-ai/haystack · error · PipelineMaxComponentRuns
Maximum run count reached for component
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.
Solutions
- Inspect the loop condition: ensure the branch that exits the cycle can actually be taken (fix the router/agent stopping logic).
- Raise the limit if more iterations are legitimate: pipeline.max_runs_per_component = <higher number> before run().
- 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
- Give every cyclic pipeline a reachable exit branch
- Set max_runs_per_component above the expected iteration count
- Test loop pipelines with a forced non-converging case in CI
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
- break_point is not a registered component in the pipeline
- Cannot connect ' ' with ' ': their declared input and…
- Cannot connect ' ' with ' ': more than one connection is…
- Component ' ' (name: ' ') not imported. Please check that…
- Component named ' ' not found in the pipeline.
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/e9e09bb1c063f819.
Report an issue: GitHub.
Appendix: 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)