## TL;DR

`GraphRecursionError` means your LangGraph run hit the step cap (default 25 supersteps) without terminating. If the loop is legitimate work that just needs more steps, pass a higher `recursion_limit` in the run config: `graph.invoke(state, {"recursion_limit": 100})`. If the graph was supposed to finish already, the limit is doing its job, do not raise it, fix the loop instead.

## The error

```text
graphrecursionerror: Graph has reached the maximum number of supersteps (25)
```

## When to use this skill

- `graph.invoke()`, `graph.stream()`, or `graph.ainvoke()` raises `GraphRecursionError` from `langgraph.errors`.
- You see the limit message at 25 supersteps (or whatever value you set).

## When NOT to use this skill

- Your graph hangs forever with no error. That is not the recursion limit; look for a blocking node, a deadlock in a shared resource, or a loop with no termination check that never reaches the cap because one step never returns.
- You want to remove the limit entirely. The limit is the only thing that keeps a bad cycle from burning through your API budget.

## Fix it: raise the limit for legitimately long runs

1. **Import the error so you can catch it.**

   ```python
   from langgraph.errors import GraphRecursionError
   ```

   Expected: the import succeeds. If it fails, you are on an old langgraph release; upgrade with `pip install -U langgraph`.

2. **Pass a higher limit in the run config.**

   ```python
   result = graph.invoke(inputs, {"recursion_limit": 100})
   ```

   Expected: the run completes and returns the final state. The default of 25 is too low for agent loops with tool calls; a ReAct agent that calls 3 tools per reasoning round burns ~6 supersteps per round, so 25 gives it about 4 rounds.

3. **Set the limit per run, not globally.** Keep small limits on simple graphs so cycles fail fast:

   ```python
   RESEARCH_RUN_CONFIG = {"recursion_limit": 100}  # deep agent loops
   QA_RUN_CONFIG = {"recursion_limit": 15}          # simple Q and A, catches cycles fast
   ```

   Expected: simple flows still crash loudly on a cycle instead of grinding to the higher ceiling.

4. **Catch the error and return partial results instead of crashing.**

   ```python
   try:
       result = graph.invoke(inputs, {"recursion_limit": 100})
   except GraphRecursionError:
       logger.error("recursion limit hit; returning partial state")
       result = last_known_state
   ```

   Expected: a runaway loop surfaces as a logged, recoverable event instead of a 500.

5. **If the graph STILL hits the new limit, the loop is a bug. Find the cycle:**
   - Enable tracing (LangSmith or `debug=True` in streaming) and watch which nodes repeat.
   - Check every conditional edge for a path back to a node you already visited. The usual bug is a router whose exit condition never becomes true (for example, an LLM node that always returns a tool call because the tool result is malformed).
   - Add a real exit condition to the router: route to `END` when `attempts >= MAX_ATTEMPTS` or when the state holds a valid answer.
   - Give the graph its own loop counter separate from the recursion limit: your own counter is the intended exit, the recursion limit is the backstop.

## Variant phrasings

### `GraphRecursionError: maximum recursion depth exceeded` in a langgraph tool loop

Same error, raised from a tool-calling agent. Fix: raise `recursion_limit` in the invoke config AND check that the tool actually succeeds. A tool that always errors back makes the LLM retry forever; the fix there is tool repair, not a higher limit.

### Recursion error in a compiled subgraph

Subgraphs share the parent's limit by default. Raise the limit on the outer invoke, or simplify: subgraphs that loop internally should expose their own explicit `MAX_STEPS` so a parent-level cap of 25 does not kill legitimate nested work.

## Why it happens

LangGraph executes graphs in supersteps, one node transition per step. To keep an infinite cycle from hanging forever, the runner raises `GraphRecursionError` after `recursion_limit` supersteps. The default is 25. Agent-style graphs with tool-calling loops routinely need 30-100 steps, so the default is too low for them, but exactly right for simple Q and A flows where hitting it always means a cycle.

## Edge cases

- **Streaming**: pass the config to `graph.stream()` the same way; the limit applies per run.
- **`astream_events`**: same; the config key is `recursion_limit` there too.
- **Very large limits (1000+)**: fine for batch agents, but a real cycle will now burn that many LLM calls before failing. Keep a wall-clock timeout alongside.
- **`recursion_limit` below your legit step count in tests**: tests with simple fixtures pass at 25 while production with retries needs 100. Set the limit from a constant per environment, not per call site, so the two stay in sync.
- **Checkpointing**: when the limit is hit the run raises, but checkpoints written by completed steps are still there. Resume from the checkpoint instead of rerunning from scratch.

## Tool compatibility

- langgraph (Python) all recent releases; `langgraph.errors.GraphRecursionError` is the documented import path.
- Applies to `StateGraph`, compiled graphs, subgraphs, and `create_react_agent`-style agent graphs.
