How to Fix 'duplicate tool calls when scaling' in CrewAI (TypeScript)
When CrewAI starts throwing duplicate tool calls when scaling, it usually means the same tool invocation is being registered more than once as your workload increases. In TypeScript, this often shows up when you scale from one agent/task to many, or when you reuse agent/task instances across concurrent runs.
The error is rarely about the tool itself. It’s usually about lifecycle, shared state, or a handler being attached twice.
The Most Common Cause
The #1 cause is reusing the same Agent, Task, or tool instance across multiple concurrent executions. CrewAI’s runtime expects each run to own its own execution context, and shared instances can cause duplicated tool registration or repeated callback wiring.
Here’s the broken pattern:
| Broken | Fixed |
|---|---|
| Reuse one agent/tool instance for every request | Create fresh instances per run |
// broken.ts
import { Agent, Task, Crew } from "crewai";
import { SerperDevTool } from "crewai-tools";
const searchTool = new SerperDevTool(); // shared singleton
const analyst = new Agent({
role: "Research Analyst",
goal: "Find relevant market data",
tools: [searchTool],
});
export async function runAnalysis(topic: string) {
const task = new Task({
description: `Research ${topic}`,
agent: analyst, // reused across runs
});
const crew = new Crew({
agents: [analyst],
tasks: [task],
});
return crew.kickoff();
}
// fixed.ts
import { Agent, Task, Crew } from "crewai";
import { SerperDevTool } from "crewai-tools";
export async function runAnalysis(topic: string) {
const searchTool = new SerperDevTool(); // fresh per run
const analyst = new Agent({
role: "Research Analyst",
goal: "Find relevant market data",
tools: [searchTool],
});
const task = new Task({
description: `Research ${topic}`,
agent: analyst,
});
const crew = new Crew({
agents: [analyst],
tasks: [task],
});
return crew.kickoff();
}
If you’re running this behind an API route, queue worker, or serverless handler, don’t hoist agents/tools into module scope unless they are explicitly stateless and documented as safe to reuse.
Other Possible Causes
Tool added twice in config and code
This happens when the same tool is declared in both the agent constructor and a wrapper layer that injects tools again.
const agent = new Agent({
role: "Support Agent",
goal: "Answer customer questions",
tools: [new SerperDevTool()],
});
// later
agent.tools?.push(new SerperDevTool()); // duplicate registration
Fix it by defining tools in one place only.
const agent = new Agent({
role: "Support Agent",
goal: "Answer customer questions",
tools: [new SerperDevTool()],
});
Duplicate event listeners or callbacks
If you attach listeners on every request without removing them, the same tool call can be handled twice.
crew.on("tool_call", handleToolCall);
crew.on("tool_call", handleToolCall); // accidental double bind
Use one registration path and guard against repeated setup.
if (!crewListenerBound) {
crew.on("tool_call", handleToolCall);
crewListenerBound = true;
}
Parallel kickoff against shared state
If two requests call kickoff() at the same time on the same crew instance, you can get duplicated internal state.
await Promise.all([
crew.kickoff(),
crew.kickoff(),
]);
Create isolated crews per job instead.
await Promise.all([
buildCrew().kickoff(),
buildCrew().kickoff(),
]);
Nested retries wrapping the same task
A retry layer outside CrewAI plus internal retries can replay the same tool action more than once.
for (let i = 0; i < 3; i++) {
await crew.kickoff(); // replays side effects if not idempotent
}
If you need retries, retry at the job boundary and make tool actions idempotent.
await retry(async () => {
return buildCrew().kickoff();
});
How to Debug It
- •
Check whether your agents/tools are singletons
- •Search for module-level
new Agent(...),new Task(...), ornew SerperDevTool(). - •If they live outside the request/job function, move them inside.
- •Search for module-level
- •
Log object identity per run
- •Print whether the same instance is reused across requests.
- •In Node.js, add simple tags:
console.log("agent instance", analyst); console.log("tool instance", searchTool);
- •
Remove concurrency first
- •Replace
Promise.all()with sequential execution. - •If the error disappears, you have a shared-state problem.
- •Replace
- •
Inspect where tools are attached
- •Make sure tools are only declared once.
- •Check wrappers, decorators, factory functions, and test setup files for duplicate injection.
Prevention
- •Build a
createCrew()factory that returns freshAgent,Task, and tool instances per execution. - •Keep tools idempotent where possible so repeated calls don’t create side effects.
- •Add a concurrency test that runs two jobs at once and asserts no duplicate tool registration or repeated callback execution.
If you’re seeing this in production, treat it as a lifecycle bug first, not a model bug. In TypeScript CrewAI apps, shared mutable objects are usually the real source of “duplicate tool calls when scaling.”
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