BaseTool.run / StructuredTool raise 'asyncio.run() cannot be called from a running event loop' when a tool returns a coroutine
@Vidit-Ostwal is already working on this.
Since Aug 26, 2026.
- Dominant language
- Python
- Stars
- 58.8k
- Forks
- 8.5k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 109
Description
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:342—BaseTool.rundoesresult = asyncio.run(result)lib/crewai/src/crewai/tools/base_tool.py:553—Tool.run(the@tooldecorator class), same patternlib/crewai/src/crewai/tools/structured_tool.py:441and:446—CrewStructuredTool.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 aThreadPoolExecutor(max_workers=1)viapool.submit(ctx.run, asyncio.run, coro).result(), elseasyncio.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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.