python/cpython · warning · RuntimeError

capture_call_graph() is called outside of a running event lo

Error message

capture_call_graph() is called outside of a running event loop and no *future* to introspect was provided

What it means

asyncio.graph.capture_call_graph() introspects the current running task's call stack. Without a future argument it needs a running event loop to find tasks.current_task(); called from synchronous code it raises this RuntimeError telling you to either call it inside the loop or pass an explicit future to introspect.

Source

Thrown at Lib/asyncio/graph.py:135

    ``abs(limit)`` entries.  If 'limit' is positive, the entries left are
    the closest to the invocation point.  If 'limit' is negative, the
    topmost entries are left.  If 'limit' is omitted or None, all entries
    are present.  If 'limit' is 0, the call stack is not captured at all,
    only "awaited by" information is present.
    """

    loop = events._get_running_loop()

    if future is not None:
        # Check if we're in a context of a running event loop;
        # if yes - check if the passed future is the currently
        # running task or not.
        if loop is None or future is not tasks.current_task(loop=loop):
            return _build_graph_for_future(future, limit=limit)
        # else: future is the current task, move on.
    else:
        if loop is None:
            raise RuntimeError(
                'capture_call_graph() is called outside of a running '
                'event loop and no *future* to introspect was provided')
        future = tasks.current_task(loop=loop)

    if future is None:
        # This isn't a generic call stack introspection utility. If we
        # can't determine the current task and none was provided, we
        # just return.
        return None

    if not isinstance(future, futures.Future):
        raise TypeError(
            f"{future!r} object does not appear to be compatible "
            f"with asyncio.Future"
        )

    call_stack: list[FrameCallGraphEntry] = []

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Call capture_call_graph() from inside a coroutine/callback running on the loop
  2. Or pass future=<the Task/Future under inspection> explicitly
  3. Capture the graph eagerly while the task is alive, store it, and report later from sync code

Example fix

# before
def sync_error_handler():
    g = asyncio.graph.capture_call_graph()  # RuntimeError

# after
async def task_body():
    g = asyncio.graph.capture_call_graph()  # inside running loop
def sync_error_handler(saved_graph):
    report(saved_graph)
Defensive patterns

Strategy: validation

Validate before calling

import asyncio
if asyncio.events._get_running_loop() is None and future is None:
    # no loop and no future: capture_call_graph() will raise
    graph = None

Type guard

def can_capture_call_graph(future=None) -> bool:
    import asyncio
    return future is not None or asyncio.events._get_running_loop() is not None

Prevention

When it happens

Trigger: Calling capture_call_graph() from a plain function during import, from a thread, or after the loop stopped, without the future parameter.

Common situations: Diagnostics/error reporters hooked into sync exception handlers; logging pipelines that try to attach asyncio context; calling from __del__ or signal handlers.

Related errors


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