How to Fix 'async event loop error in production' in CrewAI (Python)
An async event loop error in production with CrewAI usually means Python tried to run an async workflow while an event loop was already active, closed, or being managed by another framework. You’ll typically hit it when calling crew.kickoff() from FastAPI, Jupyter, Celery, or any app that already owns the loop.
In practice, the failure shows up as one of these patterns:
- •
RuntimeError: This event loop is already running - •
RuntimeError: There is no current event loop in thread 'MainThread' - •
RuntimeError: Event loop is closed
The Most Common Cause
The #1 cause is mixing sync and async incorrectly.
CrewAI gives you both sync and async entry points depending on how you structure your app. The broken pattern is usually calling a blocking CrewAI kickoff inside an async function, or trying to manually manage the loop with asyncio.run() where a loop already exists.
Broken vs fixed
| Broken pattern | Fixed pattern |
|---|---|
| Calling sync code inside an async route | Awaiting the async API properly |
Wrapping asyncio.run() inside a running loop | Using the framework’s async context |
| Letting CrewAI create/manage loops indirectly | Managing one loop at the app boundary |
# ❌ Broken: FastAPI route + blocking kickoff + nested loop risk
from fastapi import FastAPI
from crewai import Agent, Task, Crew
import asyncio
app = FastAPI()
@app.get("/run")
async def run_crew():
crew = Crew(
agents=[Agent(role="Researcher", goal="Find facts", backstory="...")],
tasks=[Task(description="Research the topic", expected_output="Summary")]
)
# Common mistake: forcing asyncio.run inside an already-running server loop
result = asyncio.run(crew.kickoff())
return {"result": result}
# ✅ Fixed: keep the route async and use the correct execution style
from fastapi import FastAPI
from crewai import Agent, Task, Crew
app = FastAPI()
@app.get("/run")
async def run_crew():
crew = Crew(
agents=[Agent(role="Researcher", goal="Find facts", backstory="...")],
tasks=[Task(description="Research the topic", expected_output="Summary")]
)
# Use the API that matches your CrewAI version and runtime model
result = await crew.kickoff_async()
return {"result": result}
If your installed CrewAI version doesn’t expose kickoff_async(), then don’t force async at the route level. Move the work into a sync worker thread or a background job queue.
Other Possible Causes
1) Running CrewAI in Jupyter or IPython with asyncio.run()
Notebook environments already have an event loop running.
# ❌ Broken
import asyncio
asyncio.run(crew.kickoff())
# ✅ Fixed
result = await crew.kickoff_async()
If you must stay in a notebook, use top-level await instead of asyncio.run().
2) Using Celery workers with an async task body
Celery tasks are usually sync unless you explicitly design for async execution.
# ❌ Broken
from celery import shared_task
@shared_task
def run_agent():
return asyncio.run(crew.kickoff())
# ✅ Fixed
@shared_task
def run_agent():
return crew.kickoff()
If your agent logic depends on async I/O, push it behind a separate service or run it in a dedicated async worker process.
3) Creating and closing event loops manually
This breaks under load when code paths reuse the same process.
# ❌ Broken
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
result = loop.run_until_complete(crew.kickoff())
loop.close()
# ✅ Fixed
result = await crew.kickoff_async()
Manual loop handling belongs at the application boundary, not inside business logic.
4) Version mismatch between CrewAI and dependencies
Older CrewAI versions and mismatched langchain, pydantic, or Python versions can trigger runtime issues that look like loop errors.
crewai==0.x.x
python==3.12
langchain==0.x.x
pydantic==2.x.x
Check for:
- •Unsupported Python version for your CrewAI release
- •Mixed old/new agent APIs in the same codebase
- •Dependency conflicts after upgrading packages
Run:
pip show crewai langchain pydantic python-dotenv
pip freeze | grep -E "crewai|langchain|pydantic"
How to Debug It
- •
Read the exact traceback
- •If you see
RuntimeError: This event loop is already running, you’re nesting loops. - •If you see
RuntimeError: Event loop is closed, something closed it too early. - •If you see
There is no current event loop in thread, you’re calling async code from a plain thread without setup.
- •If you see
- •
Find where
kickoff()is called- •Search for:
- •
asyncio.run( - •
run_until_complete( - •
.kickoff() - •
.kickoff_async()
- •
- •Check whether that call sits inside:
- •FastAPI/Starlette route handlers
- •Jupyter notebooks
- •Celery tasks
- •Background threads
- •Search for:
- •
Confirm your runtime model
- •Sync app: use
crew.kickoff() - •Async app: use
await crew.kickoff_async()if supported by your version - •Worker process: keep it sync unless you own the event-loop lifecycle
- •Sync app: use
- •
Print environment details before changing code
import sys, asyncio, crewai print(sys.version) print(crewai.__version__) try: print(asyncio.get_running_loop()) except RuntimeError as e: print(f"No running loop: {e}")
Prevention
- •Keep one execution model per boundary:
- •web request handlers stay async if needed,
- •workers stay sync unless there’s a strong reason otherwise.
- •Don’t call
asyncio.run()inside libraries, routes, or task functions. - •Pin and test dependency versions together before production deploys:
- •CrewAI,
- •Python,
- •LangChain-related packages,
- •your web framework.
If this only fails in production and not locally, assume runtime mismatch first: different server worker type, different Python version, or different dependency resolution. That’s where most CrewAI event-loop bugs hide.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit