## 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.

```text
langgraph GraphRecursionError: recursion limit of 25 reached
```

## Use 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

1. Log the route each step takes to see the loop:

```python
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__`.

2. Check the router's END condition. The usual bug is a condition that never becomes true:

```python
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.

3. Break agent-tool ping-pong by capping tool iterations in state:

```python
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 END
```
Expected output: the graph terminates after 5 tool rounds even if the agent keeps asking. This turns an infinite loop into a bounded one.

4. Only after confirming the logic is sound, raise the limit for legitimately long runs:

```python
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
