How to Fix 'chain execution stuck during development' in CrewAI (TypeScript)
When CrewAI says chain execution stuck during development, it usually means the agent pipeline is waiting on something that never resolves: a tool call, a promise, a missing output, or a dependency loop between tasks. In TypeScript projects, this shows up most often during local development when an agent runs fine for one step and then hangs forever on the next.
The important part: this is rarely a “CrewAI bug” in isolation. It’s usually your task graph, tool implementation, or async handling causing the chain to stop making progress.
The Most Common Cause
The #1 cause is an async tool or task that never returns a resolved value. In TypeScript, this often happens when you forget to return from an async function, leave a promise pending, or call an external API without timeout handling.
Here’s the broken pattern versus the fixed pattern.
| Broken | Fixed |
|---|---|
| Tool never returns data | Tool always returns a serializable result |
| Promise can hang forever | Timeout and error handling added |
| Agent waits indefinitely | Agent gets a deterministic response |
// BROKEN
import { Tool } from "@crewai/crewai";
export const fetchCustomerProfile = new Tool({
name: "fetch_customer_profile",
description: "Fetch customer profile from internal API",
execute: async ({ customerId }: { customerId: string }) => {
const res = await fetch(`https://internal-api.local/customers/${customerId}`);
const data = await res.json();
// Missing return here causes the chain to stall
JSON.stringify(data);
},
});
// FIXED
import { Tool } from "@crewai/crewai";
export const fetchCustomerProfile = new Tool({
name: "fetch_customer_profile",
description: "Fetch customer profile from internal API",
execute: async ({ customerId }: { customerId: string }) => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10_000);
try {
const res = await fetch(`https://internal-api.local/customers/${customerId}`, {
signal: controller.signal,
});
if (!res.ok) {
throw new Error(`fetch_customer_profile failed with HTTP ${res.status}`);
}
const data = await res.json();
return {
customerId,
profile: data,
};
} finally {
clearTimeout(timeout);
}
},
});
If you’re using Agent, Task, and Crew directly, the same rule applies: every step must resolve with output the next step can consume. A task that logs something but returns nothing will often trigger the “stuck during development” behavior.
Other Possible Causes
1) Circular task dependencies
If Task A depends on Task B and Task B depends on Task A, CrewAI can’t make forward progress.
const taskA = new Task({
description: "Review policy",
context: [taskB], // bad if taskB also references taskA
});
const taskB = new Task({
description: "Summarize policy",
context: [taskA],
});
Fix it by making dependencies one-directional only.
const taskA = new Task({
description: "Review policy",
});
const taskB = new Task({
description: "Summarize policy",
context: [taskA],
});
2) Non-serializable tool output
CrewAI agents expect plain JSON-like output. Returning Date, Map, class instances, or circular objects can break downstream execution.
// Bad
return {
timestamp: new Date(),
cache: new Map(),
};
// Good
return {
timestamp: new Date().toISOString(),
cacheKeys: Array.from(cache.keys()),
};
3) LLM call blocked by missing config
Sometimes the chain is not stuck in your code at all. The model call never completes because your provider config is incomplete or invalid.
const agent = new Agent({
role: "Analyst",
goal: "Review claims",
llm: "openai/gpt-4o-mini", // wrong if env vars are missing or provider isn't configured
});
Check that your environment has valid credentials and model settings:
OPENAI_API_KEY=...
CREWAI_MODEL=gpt-4o-mini
4) Tool exception swallowed by wrapper code
If your tool catches errors and returns nothing, the agent sees silence instead of failure.
execute: async () => {
try {
await dangerousCall();
} catch (err) {
console.error(err);
// no return, no throw
}
}
Prefer explicit failure:
execute: async () => {
try {
return await dangerousCall();
} catch (err) {
throw new Error(`dangerousCall failed: ${(err as Error).message}`);
}
}
How to Debug It
- •
Run with minimal graph
- •Remove all but one
Agentand oneTask. - •If the issue disappears, add tasks back one at a time until it hangs again.
- •Remove all but one
- •
Instrument every tool
- •Log before and after each
execute. - •You want to know whether the hang happens before the tool starts, inside the tool, or after it returns.
- •Log before and after each
execute: async (input) => {
console.log("[fetch_customer_profile] start", input);
const result = await doWork(input);
console.log("[fetch_customer_profile] done", result);
return result;
}
- •
Check for unresolved promises
- •Search for functions that create promises without
awaitorreturn. - •Common offenders are database calls, HTTP requests, queues, and file reads.
- •Search for functions that create promises without
- •
Validate outputs
- •Print what each task returns.
- •If you see
undefined, an empty object where text was expected, or a complex object that can’t be serialized cleanly, fix that first.
Prevention
- •Make every tool return deterministic JSON-serializable data.
- •Add timeouts around external calls so no agent step can hang forever.
- •Keep task dependencies strictly one-way; don’t build cycles in your crew graph.
- •Treat
undefinedas a bug in agent tooling code. If a step has no meaningful output, throw early instead of letting CrewAI wait on it.
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