langgraph GraphRecursionError: recursion limit of 25 reached
Fixes LangGraph GraphRecursionError when the graph hits the default 25-step recursion limit. Use when an agent loop never terminates, when two nodes bounce control back and forth, or when a router keeps sending work to the same node. Not for must-write errors, for invalid checkpoint updates, or for genuinely long workflows.
TL;DR
GraphRecursionError means the graph ran 25 steps without finishing, which is almost always an infinite loop (a router that never routes to END, or two nodes ping-ponging). Fix the loop first by logging each step's route; only raise the recursion limit if the workflow legitimately needs more than 25 steps.
langgraph GraphRecursionError: recursion limit of 25 reachedUse this when
- A LangGraph run dies with GraphRecursionError at 25 steps
- An agent loop spins without producing a final answer
- Two nodes keep handing control back to each other
Not for this skill when
- A node fails to write state (thats the must-write error)
- Checkpoint restore fails (thats a checkpoint error)
- The workflow genuinely needs 30+ steps (then raising the limit is correct)
Steps
- Log the route each step takes to see the loop:
for step in app.stream(input, stream_mode="updates"):
print(list(step.keys()))Expected output: the node names in order. A loop looks like agent, tools, agent, tools, ... repeating; a healthy run progresses toward __end__.
- Check the router's END condition. The usual bug is a condition that never becomes true:
def router(state):
if state.get("done"):
return END
return "agent"Expected output: some path sets done to true. If nothing ever sets it, the graph can never finish regardless of the limit.
- Break agent-tool ping-pong by capping tool iterations in state:
MAX_TOOL_ROUNDS = 5
def router(state):
if len(state.get("tool_calls", [])[:MAX_TOOL_ROUNDS]) == MAX_TOOL_ROUNDS:
return END
return "tools" if state.get("needs_tools") else ENDExpected output: the graph terminates after 5 tool rounds even if the agent keeps asking. This turns an infinite loop into a bounded one.
- Only after confirming the logic is sound, raise the limit for legitimately long runs:
app.invoke(input, config={"recursion_limit": 50})Expected output: the run completes under 50 steps. Raising the limit on a real loop just burns more time before the same error.
Variant phrasings
recursion limit reached but the graph looks correct
Stream the steps (step 1). "Looks correct" usually means the END branch exists but its condition references a key nothing writes.
limit of 25 reached in a fan-out graph
Parallel branches each count as steps. A wide fan-out can legitimately exceed 25; raise the limit and move on.
Why it happens
LangGraph caps steps at 25 to catch runaway graphs before they burn infinite compute. Agent loops are the classic trigger: the agent node keeps emitting tool calls, the tool node keeps returning results, and no router branch ever says stop. The limit is a symptom detector, not the disease; bumping it without fixing the routing just delays the error.
Edge cases
stream_mode="updates"itself adds no steps, so the count you see is the real step count.- Subgraph invocations count against the parent's limit; a nested loop burns the budget twice as fast.
- Setting the limit absurdly high (1000+) on a real loop can hang your process; prefer the iteration cap in step 3.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_N7shs5SBX04HHjNvGRYscQ
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.