How to Fix 'async event loop error during development' in CrewAI (TypeScript)
If you’re seeing async event loop error during development in a CrewAI TypeScript app, it usually means something in your agent startup path is trying to run async work at the wrong time or in the wrong runtime. In practice, this shows up during local dev with hot reload, top-level imports, or when mixing Node ESM/CJS patterns with CrewAI’s async initialization.
The error is almost always a symptom, not the root cause. The root cause is usually one of: double-starting the runtime, calling async code from module scope, or using an unsupported Node/runtime setup.
The Most Common Cause
The #1 cause is running async CrewAI initialization at module load time instead of inside an explicit main() function.
CrewAI classes like Crew, Agent, and task execution paths often assume a clean async boundary. If you instantiate and kick off execution during import evaluation, dev tools like tsx watch, nodemon, or Vite-style reloaders can trigger the code twice and produce errors such as:
- •
Error: async event loop error during development - •
RuntimeError: Event loop is already running - •
Cannot use await outside an async function - •
CrewAIError: Failed to initialize crew runtime
Broken vs fixed
| Broken pattern | Fixed pattern |
|---|---|
| Runs on import | Runs inside main() |
| Harder to control lifecycle | Explicit startup/shutdown |
| Breaks more often with hot reload | Stable under watch mode |
// broken.ts
import { Crew, Agent, Task } from "crewai";
const agent = new Agent({
role: "Researcher",
goal: "Research insurance claims patterns",
backstory: "You analyze claim data",
});
const task = new Task({
description: "Summarize claim anomalies",
agent,
});
// ❌ Side effect at module scope
const crew = new Crew({
agents: [agent],
tasks: [task],
});
const result = await crew.kickoff(); // triggers during import
console.log(result);
// fixed.ts
import { Crew, Agent, Task } from "crewai";
async function main() {
const agent = new Agent({
role: "Researcher",
goal: "Research insurance claims patterns",
backstory: "You analyze claim data",
});
const task = new Task({
description: "Summarize claim anomalies",
agent,
});
const crew = new Crew({
agents: [agent],
tasks: [task],
});
const result = await crew.kickoff();
console.log(result);
}
main().catch((err) => {
console.error("Crew execution failed:", err);
process.exit(1);
});
The key difference is simple: don’t execute the crew when the file is imported. Put all runtime work behind a function boundary.
Other Possible Causes
1) Mixing ESM and CommonJS incorrectly
If your project has "type": "module" but you’re using CommonJS imports, or vice versa, CrewAI startup can fail before your agent even runs.
{
"type": "module"
}
// broken
const { Crew } = require("crewai");
// fixed
import { Crew } from "crewai";
If you’re compiling TypeScript to CommonJS, keep your tsconfig.json aligned:
{
"compilerOptions": {
"module": "CommonJS",
"target": "ES2022"
}
}
2) Hot reload starting the process twice
Watch mode can re-run your entrypoint while the previous process is still alive. That leads to duplicate event loop setup and confusing async failures.
{
"scripts": {
"dev": "tsx watch src/index.ts"
}
}
If that triggers the issue, try a plain node run first:
{
"scripts": {
"dev": "node dist/index.js"
}
}
Or ensure your watcher kills the previous process cleanly before restart.
3) Calling async code inside top-level config files
This happens when environment loading or client setup returns a promise and you use it synchronously.
// broken
const apiKey = loadApiKeyFromVault(); // returns Promise<string>
const agent = new Agent({
role: "Underwriter",
goal: `Use key ${apiKey}`,
});
Fix it by resolving configuration before building agents:
async function main() {
const apiKey = await loadApiKeyFromVault();
const agent = new Agent({
role: "Underwriter",
goal: `Use key ${apiKey}`,
});
}
4) Node version too old for your runtime stack
CrewAI TypeScript projects typically expect a modern Node runtime. Older versions can produce event-loop behavior that looks random during development.
Check this first:
node -v
Use a current LTS release, ideally Node 20+ for local development. If your repo uses .nvmrc, make sure everyone matches it:
20
How to Debug It
- •
Remove hot reload
- •Run the app once with plain Node.
- •If the error disappears, your watcher is part of the problem.
- •
Search for top-level awaits and side effects
- •Look for
await crew.kickoff()outside a function. - •Look for any code that instantiates
Crew,Agent, orTaskat import time.
- •Look for
- •
Check module format
- •Confirm whether you’re using ESM or CommonJS.
- •Verify
package.jsonandtsconfig.jsonagree.
- •
Add lifecycle logging
- •Log before and after each major step:
- •env load
- •agent creation
- •crew creation
- •kickoff call
- •Log before and after each major step:
Example:
async function main() {
console.log("loading config");
console.log("creating agents");
console.log("building crew");
console.log("starting kickoff");
}
That tells you exactly where the failure starts.
Prevention
- •Keep all CrewAI execution inside an explicit
main()entrypoint. - •Match your TypeScript module settings with your package format.
- •Use one dev runner consistently; don’t mix multiple watchers on the same entry file.
- •Pin Node to a supported LTS version in
.nvmrcor.tool-versions.
If you want one rule to remember: never let CrewAI start work during module import. That’s the pattern that causes most of these development-only async event loop errors.
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