Guide

How to Debug LangGraph Agents in Production

LangGraph agents fail differently from scripts. A script stops at the line that broke. A graph keeps routing, retrying and looping, so by the time you see an error, the cause is several steps back. To debug LangGraph agents in production, build three habits: make the graph's state inspectable, make its loops bounded, and make failures land somewhere you can see them.

The error you will meet first: GraphRecursionError

When a graph runs out of steps, LangGraph raises GraphRecursionError with a message like:

Recursion limit of N reached without hitting a stop condition.
You can increase the limit by setting the `recursion_limit` config key.

The message suggests raising the limit. Usually that is the wrong first move. The error means a path through the graph never reached END, and the common cause is a conditional edge that keeps sending the agent back to the same node, for example a tool call that fails and is retried by the model forever.

Check the routing first. When the loop is genuinely long and intended, raise the limit per run:

graph.invoke(inputs, {"recursion_limit": 100})

or set it once on the compiled graph:

graph = builder.compile().with_config(recursion_limit=100)

If you use the prebuilt ReAct agent, it tracks remaining_steps and returns a final message ("Sorry, need more steps to process this request.") instead of raising, once it is nearly out of steps. That is better for users, and worse for debugging, because the failure no longer looks like an error. Log it.

Make state inspectable with a checkpointer

Most production debugging questions are "what did the agent know at step N?". A checkpointer answers that. Compile with one and give every run a thread_id:

graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "ticket-4812"}}
graph.invoke(inputs, config)

state = graph.get_state(config)          # current values and the next node
history = list(graph.get_state_history(config))   # every checkpoint, newest first

Use a durable checkpointer in production, not the in-memory one, or the history disappears with the process. Use a thread_id you can find later, such as a ticket or request ID, so a bug report leads straight to its run.

Replay from the step that went wrong

Once you have the history, you can rerun from any checkpoint by passing its config to invoke:

before = next(s for s in history if s.next == ("call_tool",))
graph.invoke(None, before.config)

Nodes before that checkpoint are not re-executed. Nodes after it are, including LLM calls and API requests, so replaying against production tools has real side effects. Point replays at a staging environment, or at tools that are safe to call twice.

To test a fix, update_state on a past checkpoint creates a fork with changed values, and invoking from the fork shows whether the graph now takes the path you expect. Subgraphs replay as a single step unless they were compiled with their own checkpointer (checkpointer=True).

Stream node updates while it runs

For live debugging, stream what each node returns instead of waiting for the final result:

for chunk in graph.stream(inputs, stream_mode="updates", version="v2"):
    if chunk["type"] == "updates":
        for node, update in chunk["data"].items():
            print(node, update)

A loop shows up quickly as the same node name repeating with nearly identical updates.

Bound failures instead of letting them cascade

A single flaky tool should not take down a run. In recent LangGraph versions you can set retry, timeout and error handling for every node at once:

graph = (
    StateGraph(State)
    .set_node_defaults(
        retry_policy=RetryPolicy(max_attempts=3),
        timeout=TimeoutPolicy(run_timeout=30),
        error_handler=record_failure,
    )
    ...
)

The error handler runs after retries are exhausted. It can write the failure into state, or return a Command that routes to a recovery node. Values set on an individual node override the defaults. Either way, the failure becomes a state change you can inspect later instead of an exception that vanished with the process.

A checklist to debug LangGraph agents in production

If you are running CrewAI as well, the same thinking applies there. See debugging CrewAI agents in production. For what to capture across a fleet of agents, see AI agent observability.

When you want someone else to look

FleetHelp's managed support is for teams running LangGraph, CrewAI or custom agents in production who want an experienced second pair of eyes on stuck runs, loops and failures, without hiring for it.

Frequently Asked Questions

How do I fix GraphRecursionError in LangGraph?

First find out why the graph never reached a stop condition, usually a conditional edge that keeps routing back to the same node. Only raise the limit once the loop is intentional, by passing recursion_limit in the run config or baking it in with with_config.

How do I see what my LangGraph agent did step by step?

Compile the graph with a checkpointer and give each run a thread_id. get_state shows the current state and the next node, and get_state_history lists every checkpoint, newest first.

Can I rerun a LangGraph agent from the middle?

Yes. Pass a past checkpoint's config to invoke. Nodes before that checkpoint are not re-run, and nodes after it run again, including their LLM and API calls.

How do I stop one failing tool call from breaking the whole graph?

Give the node a retry policy and an error handler. In recent LangGraph versions you can set these for every node with set_node_defaults, and the handler can record the failure or route the graph to a recovery step.