n4nAI

Why CrewAI crews get stuck in infinite loops

Practical guide to CrewAI infinite loop debugging: trace agent delegation, set timeouts, cap iterations, and fix prompt loops in multi-agent crews.

n4n Team4 min read815 words

Audio narration

Coming soon — every post will get a voice note here.

CrewAI infinite loop debugging starts with accepting that crews rarely hang without a cause. The framework’s agents iterate until they hit an internal stop condition, and when that condition is missing or contradictory, they cycle through delegation, tool calls, or self-reflection forever. This guide lays out an ordered path to find and break those cycles in production.

1. Reproduce with verbose logging

You cannot fix a loop you cannot see. Run the crew with verbose=True on both agents and crew, and capture stdout to a file. CrewAI prints each agent’s thought, action, and observation. A loop shows up as repeated substrings in the log within seconds.

from crewai import Agent, Task, Crew

agent = Agent(
    role="Analyst",
    goal="Summarize the report",
    backstory="Precise and terse.",
    verbose=True,
)
task = Task(
    description="Summarize the attached text in 2 sentences.",
    expected_output="Two-sentence summary.",
    agent=agent,
)
crew = Crew(agents=[agent], tasks=[task], verbose=True)
crew.kickoff()

Pipe output: python crew.py 2>&1 | tee crew.log. Grep for the agent role name. If you see the same Action: line more than five times, you have a loop.

2. Classify the loop type

Three patterns cover nearly all CrewAI hangs.

Delegation loops

Two agents with allow_delegation=True ping-pong a task. Agent A delegates to B, B delegates back to A because its goal is ambiguous. The crew never terminates because neither agent marks the task done.

Self-reflection loops

A single agent hits its internal ReAct loop. It keeps generating Thought: and Action: blocks without emitting a final answer. This is the default behavior when max_iter is high and the prompt lacks a stop instruction.

Tool-call loops

An agent calls a tool that returns a soft error (“try again”) or an empty string. The agent interprets that as incomplete and calls again with identical arguments.

3. Cap iterations and enforce timeouts

CrewAI exposes max_iter on the Agent to bound self-reflection. Set it to a low value during debugging—three is enough to see intent without spinning.

agent = Agent(
    role="Analyst",
    goal="Summarize the report",
    backstory="Precise and terse.",
    max_iter=3,
    allow_delegation=False,
    verbose=True,
)

max_iter does not cap wall-clock time. Wrap kickoff_async in an asyncio timeout so a stuck crew cannot block your service.

import asyncio

async def run_with_timeout(crew, seconds=30):
    return await asyncio.wait_for(crew.kickoff_async(), timeout=seconds)

try:
    result = asyncio.run(run_with_timeout(crew, 30))
except asyncio.TimeoutError:
    # emit metric, alert, fallback
    print("Crew timed out — loop suspected")

Tradeoff: a timeout kills legitimate long tasks. Set it from historical p95 latency of the same crew, not a guess.

4. Kill delegation loops with explicit boundaries

If you see cross-agent delegation, disable it unless you specifically need hierarchical control.

manager = Agent(role="Manager", goal="Coordinate", backstory="...", allow_delegation=False)
worker = Agent(role="Worker", goal="Execute", backstory="...", allow_delegation=False)

When you do need delegation, give the manager a hard exit condition in its goal: “Delegate subtasks, then call finish with the consolidated answer. Never return a task to a previous agent.” CrewAI respects natural language stop cues only as well as the underlying LLM does, so pair this with max_iter.

5. Fix the prompt contract

Most self-reflection loops come from underspecified expected_output. The agent does not know what “done” looks like. Make it unambiguous.

task = Task(
    description=(
        "Read the input. Output exactly one JSON object with keys 'summary' "
        "(string) and 'confidence' (float 0-1). Do not output any other text. "
        "If unsure, set confidence to 0.0. STOP after the JSON."
    ),
    expected_output="Single JSON object, no prose.",
    agent=agent,
)

Add a literal STOP token in the description. Many LLMs trained on ReAct traces treat STOP as termination.

6. Guard tool calls with sentinels

Tool-call loops vanish when the tool returns a decisive result. Instead of raising an exception or returning "", return a structured sentinel the agent is told to respect.

def search(query: str) -> str:
    res = backend.search(query)
    if not res:
        return "RESULT_EMPTY: no matches, do not retry with same query."
    return res

In the agent’s backstory, state: “If a tool returns RESULT_EMPTY, change the query or finish.” Without that instruction, the model treats empty as transient.

7. Detect state repetition externally

CrewAI has no built-in cycle detector across tasks. Add one by hashing the last agent message after each step using a lightweight callback or by polling the log.

import hashlib

class LoopGuard:
    def __init__(self, max_repeats=3):
        self.seen = {}
        self.max_repeats = max_repeats

    def check(self, state: str):
        h = hashlib.md5(state.encode()).hexdigest()
        self.seen[h] = self.seen.get(h, 0) + 1
        if self.seen[h] > self.max_repeats:
            raise RuntimeError("State repeated — aborting crew")

Feed it the concatenated agent.last_message after each kickoff iteration if you drive the crew manually in a loop, or parse streaming logs in a separate thread.

8. Handle LLM provider degradation

A subtle source of loops is the agent receiving truncated or error responses and retrying. If your crew uses a single provider and that provider rate-limits, the SDK may hang or return garbage that the agent loops on. An OpenAI-compatible gateway with automatic fallback when a provider is degraded—such as n4n.ai—removes the retry storm at the network layer, but you still need the client-side caps above because a fallback model can also produce ambiguous output.

Set max_rpm on the Crew to avoid hammering any provider:

crew = Crew(agents=[agent], tasks=[task], verbose=True, max_rpm=10)

9. Validate with a minimal crew

Before redeploying, strip the crew to one agent and one task that previously looped. Confirm it terminates within max_iter and timeout. Then add agents back one at a time. This isolates whether the loop was in a single agent’s reasoning or in cross-agent interaction.

minimal = Crew(agents=[agent], tasks=[task], verbose=True, max_rpm=5)
minimal.kickoff()

Common pitfalls and tradeoffs

  • Setting max_iter too low. Agents may return partial work. Use 3–5 for debugging, 10–15 for production if logs show clean termination.
  • Disabling delegation globally. You lose hierarchical planning. Disable per-agent where the loop occurs, not blanket.
  • Timeouts as a crutch. A timeout hides the bug; it does not fix the prompt. Treat every timeout as a failed test.
  • Over-specifying expected_output. If you demand a rigid format the model rarely produces, you create a new loop. Match the format to the model’s strengths (JSON for Claude, loose bullets for GPT-style).
  • Ignoring tool latency. A slow tool looks like a hang. Measure tool call duration separately from agent reasoning.

CrewAI infinite loop debugging is mostly about making termination explicit: cap iterations, bound time, forbid needless delegation, and give the model a clear “done” shape. Do that, and the cycles disappear.

Tagscrewaimulti-agentdebuggingloops

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All crewai & autogen multi-agent debugging posts →