python/cpython · error · TypeError

a coroutine was expected, got {coro!r}

Error message

a coroutine was expected, got {coro!r}

What it means

TypeError raised by Task.__init__ when the first argument is not a coroutine object (checked with coroutines.iscoroutine). Tasks wrap exactly one coroutine; passing anything else — an async function itself, a plain function, a generator, a Future — is rejected immediately (with pending-coroutine logging disabled since nothing valid exists to destroy).

Source

Thrown at Lib/asyncio/tasks.py:93

    #   as it schedules __wakeup() to be called (which calls __step() so
    #   we way that __step() is scheduled).
    # * It transitions from 2 to 3 when __step() is executed, and it clears
    #   _fut_waiter to None.

    # If False, don't log a message if the task is destroyed while its
    # status is still pending
    _log_destroy_pending = True

    def __init__(self, coro, *, loop=None, name=None, context=None,
                 eager_start=False):
        super().__init__(loop=loop)
        if self._source_traceback:
            del self._source_traceback[-1]
        if not coroutines.iscoroutine(coro):
            # raise after Future.__init__(), attrs are required for __del__
            # prevent logging for pending task in __del__
            self._log_destroy_pending = False
            raise TypeError(f"a coroutine was expected, got {coro!r}")

        if name is None:
            self._name = f'Task-{_task_name_counter()}'
        else:
            self._name = str(name)

        self._num_cancels_requested = 0
        self._must_cancel = False
        self._fut_waiter = None
        self._coro = coro
        if context is None:
            self._context = contextvars.copy_context()
        else:
            self._context = context

        if eager_start and self._loop.is_running():
            self.__eager_start()
        else:

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Call the function: create the coroutine object first — coro = my_async(); then Task(coro) / asyncio.create_task(my_async())
  2. For generic awaitables (Futures, custom __await__), use asyncio.ensure_future() which wraps appropriately
  3. Double-check higher-order APIs: registries and dispatch maps should store functions but invoke them as value() when spawning

Example fix

// before
task = asyncio.create_task(poll_service)      # function, not coroutine

// after
task = asyncio.create_task(poll_service())
Defensive patterns

Strategy: type-guard

Validate before calling

null

Type guard

import inspect

def spawn(loop, awaitable):
    if inspect.iscoroutinefunction(awaitable):
        raise TypeError('forgot to call: pass the coroutine object, not the function')
    if not inspect.iscoroutine(awaitable):
        return asyncio.ensure_future(awaitable)
    return loop.create_task(awaitable)

Try / catch

null

Prevention

When it happens

Trigger: asyncio.Task(my_async_func) instead of asyncio.Task(my_async_func()); same for loop.create_task(async_func); passing a coroutine function reference stored in a variable; passing a functools.partial of an async function without calling it; on non-default loops, types registered with a different event loop policy may also fail iscoroutine.

Common situations: Forgetting the parentheses — the most common form; passing a callback registry value that is a function not a coroutine object; mixing awaitable objects (Futures, objects with __await__) that are not coroutines and must go through ensure_future; refactors from callbacks to async that left a call operator behind.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/e874ee44c15f5dc5. Report an issue: GitHub.