How to Fix 'async event loop error during development' in CrewAI (TypeScript)

By Cyprian AaronsUpdated 2026-04-21
async-event-loop-error-during-developmentcrewaitypescript

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 patternFixed pattern
Runs on importRuns inside main()
Harder to control lifecycleExplicit startup/shutdown
Breaks more often with hot reloadStable 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

  1. Remove hot reload

    • Run the app once with plain Node.
    • If the error disappears, your watcher is part of the problem.
  2. Search for top-level awaits and side effects

    • Look for await crew.kickoff() outside a function.
    • Look for any code that instantiates Crew, Agent, or Task at import time.
  3. Check module format

    • Confirm whether you’re using ESM or CommonJS.
    • Verify package.json and tsconfig.json agree.
  4. Add lifecycle logging

    • Log before and after each major step:
      • env load
      • agent creation
      • crew creation
      • kickoff call

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 .nvmrc or .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

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

Related Guides