deepset-ai/haystack · error · ComponentError

Method 'run_async' of component '{cls.__name__}' must be a c

Error message

Method 'run_async' of component '{cls.__name__}' must be a coroutine

What it means

When a component class is instantiated, ComponentMeta.__call__ checks that run_async (if defined) is an async coroutine function via inspect.iscoroutinefunction. A plain (non-async) run_async method raises ComponentError because Haystack pipelines await run_async in async pipelines.

Source

Thrown at haystack/core/component/component.py:310

            instance = super().__call__(*args, **kwargs)
        else:
            try:
                pre_init_hook.in_progress = True
                named_positional_args = ComponentMeta._positional_to_kwargs(cls, args)
                assert set(named_positional_args.keys()).intersection(kwargs.keys()) == set(), (
                    "positional and keyword arguments overlap"
                )
                kwargs.update(named_positional_args)
                pre_init_hook.callback(cls, kwargs)
                instance = super().__call__(**kwargs)
            finally:
                pre_init_hook.in_progress = False

        # Before returning, we have the chance to modify the newly created
        # Component instance, so we take the chance and set up the I/O sockets
        has_async_run = hasattr(instance, "run_async")
        if has_async_run and not inspect.iscoroutinefunction(instance.run_async):
            raise ComponentError(f"Method 'run_async' of component '{cls.__name__}' must be a coroutine")
        instance.__haystack_supports_async__ = has_async_run

        ComponentMeta._parse_and_set_input_sockets(cls, instance)
        ComponentMeta._parse_and_set_output_sockets(instance)

        # Since a Component can't be used in multiple Pipelines at the same time
        # we need to know if it's already owned by a Pipeline when adding it to one.
        # We use this flag to check that.
        instance.__haystack_added_to_pipeline__ = None

        return instance


def _component_repr(component: Component) -> str:
    """
    All Components override their __repr__ method with this one.

    It prints the component name and the input/output sockets.

View on GitHub (pinned to e318778c9b)

Solutions

  1. Change `def run_async` to `async def run_async`
  2. If a decorator wraps run_async, ensure the wrapper preserves the coroutine (use functools.wraps on an async wrapper or return the coroutine function)
  3. If async is not needed, remove run_async and keep only run

Example fix

# before
class Echo:
    @component.output_types(out=str)
    def run(self, x: str):
        return {"out": x}
    def run_async(self, x: str):  # not a coroutine
        return self.run(x)

# after
class Echo:
    @component.output_types(out=str)
    def run(self, x: str):
        return {"out": x}
    @component.output_types(out=str)
    async def run_async(self, x: str):
        return {"out": x}
Defensive patterns

Strategy: validation

Validate before calling

import inspect
if hasattr(MyComponent, "run_async") and not inspect.iscoroutinefunction(MyComponent.run_async):
    raise TypeError("run_async must be declared with async def")

Type guard

def is_valid_async_component(instance) -> bool:
    ra = getattr(instance, "run_async", None)
    return ra is None or inspect.iscoroutinefunction(ra)

Try / catch

try:
    comp = MyComponent()
except ComponentError as e:
    if "must be a coroutine" in str(e):
        logging.error("Declare run_async with 'async def'")
    raise

Prevention

When it happens

Trigger: Defining `def run_async(...)` without `async def` on a class decorated with @component while also having a run method; wrapping run_async with a non-async decorator that loses coroutine-ness (e.g. functools.wraps over a sync wrapper, some middleware/decorators); assigning a sync bound function to instance.run_async.

Common situations: Copy-pasting sync run into run_async but forgetting the async keyword; applying a custom decorator to run_async that returns a plain function; migrating code where run_async was previously sync-tolerated.

Related errors


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