How to Fix 'duplicate tool calls when scaling' in CrewAI (TypeScript)

By Cyprian AaronsUpdated 2026-04-21
duplicate-tool-calls-when-scalingcrewaitypescript

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:

BrokenFixed
Reuse one agent/tool instance for every requestCreate 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

  1. Check whether your agents/tools are singletons

    • Search for module-level new Agent(...), new Task(...), or new SerperDevTool().
    • If they live outside the request/job function, move them inside.
  2. 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);
      
  3. Remove concurrency first

    • Replace Promise.all() with sequential execution.
    • If the error disappears, you have a shared-state problem.
  4. 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 fresh Agent, 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

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