How to Fix 'duplicate tool calls during development' in CrewAI (TypeScript)

By Cyprian AaronsUpdated 2026-04-21
duplicate-tool-calls-during-developmentcrewaitypescript

What the error means

duplicate tool calls during development usually means CrewAI saw the same tool invocation more than once in a single agent run. In TypeScript projects, this typically shows up when you accidentally register the same tool twice, call the same task twice from your orchestration code, or create multiple agent instances that share the same tool object.

The failure often appears during local development because hot reload, repeated run() calls, or test retries make the duplication visible fast. The stack trace usually points at Agent, Task, or a tool wrapper like SerperDevTool, FileReadTool, or a custom BaseTool implementation.

The Most Common Cause

The #1 cause is double-registering tools on an agent, then also passing the same tool again at task execution time or inside a rerun path.

Here’s the broken pattern:

BrokenFixed
Tool attached in multiple placesTool attached once, at one layer
Agent recreated on every requestAgent reused or created once per run
Task rerun with same side effectsTask idempotent and single execution
// BROKEN
import { Agent, Task } from "@crewai/typescript";
import { SerperDevTool } from "@crewai/tools";

const searchTool = new SerperDevTool();

export async function handleRequest() {
  const agent = new Agent({
    role: "Researcher",
    goal: "Find relevant company info",
    tools: [searchTool],
  });

  const task = new Task({
    description: "Search for Acme Corp news",
    agent,
    tools: [searchTool], // duplicate registration
  });

  // In dev, this may run twice via hot reload or request retry
  return await task.execute();
}
// FIXED
import { Agent, Task } from "@crewai/typescript";
import { SerperDevTool } from "@crewai/tools";

const searchTool = new SerperDevTool();

export async function handleRequest() {
  const agent = new Agent({
    role: "Researcher",
    goal: "Find relevant company info",
    tools: [searchTool], // register once here
  });

  const task = new Task({
    description: "Search for Acme Corp news",
    agent,
  });

  return await task.execute();
}

If you are using a custom tool class, the same rule applies. Don’t instantiate and attach it in both a shared module and a per-request handler unless you know exactly why.

Other Possible Causes

1) React/Next.js hot reload re-running setup code

If your CrewAI setup lives at module scope in dev mode, Fast Refresh can execute it more than once.

// BAD
const crew = buildCrew(); // runs again on refresh

export default async function page() {
  return await crew.kickoff();
}

Move construction behind a singleton or lazy initializer:

let crewInstance: ReturnType<typeof buildCrew> | null = null;

function getCrew() {
  if (!crewInstance) crewInstance = buildCrew();
  return crewInstance;
}

2) Calling kickoff() and execute() in the same flow

Some teams wrap a task and then also trigger the whole crew pipeline.

// BAD
await task.execute();
await crew.kickoff(); // repeats the same tool path

Pick one entry point per request path:

// GOOD
await crew.kickoff();

3) Shared mutable tool instances across parallel requests

If your tool keeps internal state, parallel requests can look like duplicates.

class MyTool extends BaseTool {
  private lastQuery = "";

  async execute(input: string) {
    this.lastQuery = input;
    return fetchSomething(input);
  }
}

Make tools stateless or create one instance per request:

export function createTools() {
  return [new MyTool()];
}

4) Retrying the same job without deduping request IDs

A queue worker that retries on timeout can replay the same tool call.

const jobId = req.headers["x-request-id"];

if (processedJobs.has(jobId)) return;
processedJobs.add(jobId);
await crew.kickoff();

If you don’t dedupe by request/job ID, CrewAI will happily do the work twice.

How to Debug It

  1. Log every entry point

    • Add logs before new Agent(...), new Task(...), and kickoff().
    • If you see two constructions for one HTTP request, you found it.
  2. Print tool identities

    • Log the tool name and object reference path.
    • If the same tool appears in both Agent.tools and Task.tools, remove one.
  3. Disable hot reload temporarily

    • Run production-style Node execution instead of dev server refresh.
    • If the error disappears, your issue is module re-execution.
  4. Check for retries and double submits

    • Inspect API gateway retries, queue redelivery, and frontend double-clicks.
    • Add an idempotency key and log it with each crew run.

A practical debug snippet:

console.log("building agent");
console.log("tool count:", agent.tools?.length);

console.log("building task");
console.log("task has tools:", Boolean((task as any).tools));

console.log("starting kickoff", { requestId });
await crew.kickoff();

If those logs appear twice for one user action, stop looking at CrewAI first. Your app is invoking it twice.

Prevention

  • Register each tool in exactly one place: either on the Agent or in your orchestration layer, not both.
  • Keep Crew construction out of top-level module code in Next.js/React dev environments.
  • Make task execution idempotent with request IDs so retries don’t become duplicate tool calls.

If you still see duplicate tool calls during development, strip your app down to one agent, one task, one tool, one invocation path. In practice, that isolates the bug faster than chasing stack traces through hot-reloaded TypeScript.


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