How to Fix 'duplicate tool calls during development' in CrewAI (Python)
What this error means
If you’re seeing duplicate tool calls during development in CrewAI, it usually means the same tool invocation is being triggered more than once in a single run. In practice, this shows up when an agent retries, when your task logic runs twice, or when you accidentally register the same tool in multiple places.
The annoying part is that the stack trace often points at CrewAI internals, not your actual bug. The fix is usually in your agent/task wiring, not in the tool itself.
The Most Common Cause
The #1 cause is duplicating the same tool call path by reusing mutable state or invoking crew.kickoff() more than once during app startup or debugging.
A very common pattern is creating agents/tasks at module import time and then calling kickoff from code that gets executed twice, such as a hot-reload loop, notebook cell rerun, or FastAPI startup hook.
Broken vs fixed pattern
| Broken pattern | Fixed pattern |
|---|---|
Calls kickoff() at import time or inside code that runs twice | Wraps execution in a single entrypoint |
Reuses the same mutable tools list across runs | Builds fresh agent/crew objects per run |
| Hard to tell where duplicate calls come from | Clear lifecycle boundary |
# broken.py
from crewai import Agent, Task, Crew
from crewai_tools import SerperDevTool
search_tool = SerperDevTool()
tools = [search_tool]
agent = Agent(
role="Researcher",
goal="Find company info",
backstory="You research companies",
tools=tools,
)
task = Task(
description="Search for ACME Corp",
expected_output="Company summary",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
# BAD: this can run multiple times in dev environments
result = crew.kickoff()
print(result)
# fixed.py
from crewai import Agent, Task, Crew
from crewai_tools import SerperDevTool
def build_crew():
search_tool = SerperDevTool()
agent = Agent(
role="Researcher",
goal="Find company info",
backstory="You research companies",
tools=[search_tool],
)
task = Task(
description="Search for ACME Corp",
expected_output="Company summary",
agent=agent,
)
return Crew(agents=[agent], tasks=[task])
if __name__ == "__main__":
crew = build_crew()
result = crew.kickoff()
print(result)
If you’re using Streamlit, FastAPI reload mode, Jupyter, or any dev server with auto-reload, this is the first thing to fix.
Other Possible Causes
1. The same tool is attached at both agent and task level
CrewAI can end up resolving the same tool more than once if you pass it through multiple layers.
# broken
agent = Agent(..., tools=[search_tool])
task = Task(..., agent=agent, tools=[search_tool]) # duplicate registration
Fix it by attaching tools in one place only.
# fixed
agent = Agent(..., tools=[search_tool])
task = Task(..., agent=agent)
2. Your custom tool method is not idempotent
If the LLM retries the call or your code invokes the same action twice, a non-idempotent tool can make the issue look like a CrewAI bug.
class WriteToCRMTool:
def _run(self, customer_id: str):
# bad: creates a new record every time
crm.create_note(customer_id=customer_id, note="Follow up")
Make it safe to repeat.
class WriteToCRMTool:
def _run(self, customer_id: str):
existing = crm.find_note(customer_id=customer_id, note="Follow up")
if existing:
return "Note already exists"
crm.create_note(customer_id=customer_id, note="Follow up")
return "Note created"
3. Your agent has overlapping instructions that trigger repeated tool use
If your prompt says “keep searching until you’re certain” and also asks for multiple sources, the model may call the same search tool repeatedly.
agent = Agent(
role="Analyst",
goal="Research thoroughly and verify everything multiple times",
backstory="...",
tools=[search_tool],
)
Tighten the instruction:
agent = Agent(
role="Analyst",
goal="Find 3 relevant sources and stop",
backstory="...",
tools=[search_tool],
)
4. You are reusing a global Crew instance across requests
This happens a lot in web apps and notebooks.
# broken: global singleton reused across requests
crew = build_crew()
@app.post("/run")
def run():
return crew.kickoff()
Build per request instead:
@app.post("/run")
def run():
crew = build_crew()
return crew.kickoff()
How to Debug It
- •
Check whether your entrypoint runs twice
- •Add a log before
kickoff(). - •If you see it twice on one request/run, you have an app lifecycle problem.
- •Common offenders:
uvicorn --reload, Streamlit reruns, notebook cells.
- •Add a log before
- •
Print the final tools attached to each agent
- •Confirm each tool appears once.
- •Look for accidental duplication from config merges or helper functions.
print([type(t).__name__ for t in agent.tools])
- •
Disable retries temporarily
- •If your setup uses retry logic around
Crew.kickoff(), turn it off. - •A retry wrapper can make one failure look like repeated duplicate calls.
- •If your setup uses retry logic around
- •
Reduce to one task and one tool
- •Start with a single
Agent, singleTask, singleSerperDevTool. - •If the error disappears, add pieces back until it returns.
- •That tells you whether the issue is lifecycle, prompt-driven repetition, or duplicated wiring.
- •Start with a single
Prevention
- •Build crews inside functions, not at module import time.
- •Attach each tool in exactly one place: either on the
Agentor via your own orchestration layer. - •Make custom tools idempotent so repeated calls don’t create duplicate side effects.
- •In dev mode, watch for auto-reload behavior that reruns startup code and triggers extra
Crew.kickoff()executions.
If you want one rule to remember: treat CrewAI objects as runtime instances, not global singletons. That alone eliminates most duplicate tool call issues during development.
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