Fix: LangGraph GraphRecursionError (Recursion Limit Reached)
Updated 2026-10-15
The error looks like this:
langgraph.errors.GraphRecursionError: Recursion limit of 25 reached without hitting a stop condition.
You can increase the limit by setting the `recursion_limit` config key.
LangGraph counts super-steps (each round of node executions). The default limit is 25. Hitting it means either your task genuinely needs more steps, or, far more often, your graph loops.
Prerequisites
- A LangGraph graph (e.g.
create_react_agentor a customStateGraph)
Steps
1. Decide: real work or a loop?
Stream the run and watch which nodes repeat:
for chunk in graph.stream({"messages": [("user", "...")]}, stream_mode="updates"):
print(list(chunk.keys()))
If you see agent → tools → agent → tools ... with the same tool call each time, it’s a loop. Go to step 3.
2. Raise the limit (when the work is real)
recursion_limit goes in the config, not the input:
result = graph.invoke(
{"messages": [("user", "Research these 10 companies...")]},
config={"recursion_limit": 60},
)
A common mistake is putting it in the input dict, where it’s ignored.
3. Fix the loop: check your conditional edge
In a custom graph, the router must return END when there are no tool calls:
from langgraph.graph import END
def should_continue(state):
last = state["messages"][-1]
return "tools" if getattr(last, "tool_calls", None) else END
builder.add_conditional_edges("agent", should_continue, ["tools", END])
With the prebuilt tools_condition from langgraph.prebuilt, this is handled for you.
4. Fix the loop: make tool results useful
If a tool returns empty or vague output, the model calls it again. Return explicit text (“No results for X; don’t retry”). See Fix: AI agent stuck in a loop.
5. Fail gracefully
from langgraph.errors import GraphRecursionError
try:
result = graph.invoke(inputs, config={"recursion_limit": 30})
except GraphRecursionError:
result = {"messages": [("assistant", "Sorry, I couldn't complete that in time.")]}
Common mistakes
Setting recursion_limit on compile(): it’s a run-time config value, passed to invoke/stream.
Raising the limit to 1000 to make the error go away: hides a loop and multiplies cost.
Subgraphs: the config propagates to subgraphs, so a looping subgraph uses up the parent’s budget.