Skip to content

BaseTool.run / StructuredTool raise 'asyncio.run() cannot be called from a running event loop' when a tool returns a coroutine #6978

Description

@Shailendra005

Description

Executing a tool whose _run (or wrapped func) returns a coroutine raises RuntimeError: asyncio.run() cannot be called from a running event loop whenever the call happens inside an already-running event loop — e.g. a FastAPI request handler, a Jupyter cell, or any async def. Synchronous scripts are unaffected, which is why this slips through basic testing but breaks every async-server deployment.

Location

  • lib/crewai/src/crewai/tools/base_tool.py:342BaseTool.run does result = asyncio.run(result)
  • lib/crewai/src/crewai/tools/base_tool.py:553Tool.run (the @tool decorator class), same pattern
  • lib/crewai/src/crewai/tools/structured_tool.py:441 and :446CrewStructuredTool.invoke, same pattern
result = self._run(*args, **kwargs)
if asyncio.iscoroutine(result):
    result = asyncio.run(result)   # <-- raises under a running loop

In-repo precedent (this is a known pattern here)

The codebase already solves exactly this elsewhere and the tool paths simply don't use it:

  • lib/crewai/src/crewai/tasks/llm_guardrail.py:24_run_coroutine_sync: try: asyncio.get_running_loop() and, if a loop is running, run the coroutine in a ThreadPoolExecutor(max_workers=1) via pool.submit(ctx.run, asyncio.run, coro).result(), else asyncio.run(coro).
  • Same guard in tools/mcp_native_tool.py:86, a2a/utils/agent_card.py, a2a/utils/delegation.py, mcp/tool_resolver.py, project/wrappers.py, project/annotations.py.

Reproduction

import asyncio
from crewai.tools import tool

@tool("echo")
async def echo(value: str) -> str:
    """Echo the value."""
    await asyncio.sleep(0.01)
    return f"echo: {value}"

async def handler():          # simulates a FastAPI route / Jupyter cell
    return echo.run(value="hi")

asyncio.run(handler())
# RuntimeError: asyncio.run() cannot be called from a running event loop

The same failure occurs with a BaseTool subclass whose _run returns a coroutine and with CrewStructuredTool.from_function(func=<async fn>).invoke(...).

Expected

run() / invoke() return the coroutine's result regardless of whether an event loop is already running, consistent with llm_guardrail._run_coroutine_sync.

Environment

crewai main (v1.15.15 snapshot), Python 3.13.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions