How to Fix 'chain execution stuck' in LangGraph (Python)

By Cyprian AaronsUpdated 2026-04-21
chain-execution-stucklanggraphpython

What “chain execution stuck” usually means

In LangGraph, chain execution stuck usually means your graph hit a state where no node can make progress. In practice, that’s almost always one of three things: a missing edge, a node that never returns the expected state update, or a conditional branch that routes to nowhere.

You’ll typically see this when invoking graph.invoke(...), streaming with graph.stream(...), or running an agent loop where the graph keeps waiting for a next step that never arrives.

The Most Common Cause

The #1 cause is a node that does not return the state shape your graph expects. In LangGraph, nodes must return a dict-like partial state update. If you mutate state in place and return None, or return the wrong keys, the graph can stall because downstream routing logic never sees the expected transition.

Here’s the broken pattern and the fixed pattern side by side:

BrokenFixed
```python
from langgraph.graph import StateGraph, END
from typing import TypedDict

class State(TypedDict): messages: list

def add_message(state: State): state["messages"].append("hello") # returns None implicitly

builder = StateGraph(State) builder.add_node("add_message", add_message) builder.set_entry_point("add_message") builder.add_edge("add_message", END)

graph = builder.compile() graph.invoke({"messages": []}) |python from langgraph.graph import StateGraph, END from typing import TypedDict

class State(TypedDict): messages: list

def add_message(state: State): return {"messages": state["messages"] + ["hello"]}

builder = StateGraph(State) builder.add_node("add_message", add_message) builder.set_entry_point("add_message") builder.add_edge("add_message", END)

graph = builder.compile() graph.invoke({"messages": []})


The broken version mutates `state` in place and returns nothing. That works like normal Python, but LangGraph expects explicit state updates so it can track transitions deterministically.

A more realistic failure shows up with conditional routing:

| Broken | Fixed |
|---|---|
|```python
from langgraph.graph import StateGraph, END

def route(state):
    if state["done"]:
        return "finish"
    return "continue"

builder = StateGraph(dict)
builder.add_node("router", lambda s: s)
builder.add_conditional_edges(
    "router",
    route,
    {
        "finish": END,
        # missing "continue"
    }
)
```|```python
from langgraph.graph import StateGraph, END

def route(state):
    if state["done"]:
        return "finish"
    return "continue"

builder = StateGraph(dict)
builder.add_node("router", lambda s: s)
builder.add_conditional_edges(
    "router",
    route,
    {
        "finish": END,
        "continue": "router",
    }
)
```|

If the router returns `"continue"` but there is no matching edge, LangGraph has nowhere to go. That often manifests as a stuck run rather than a clean Python exception, depending on how you’ve wrapped execution.

## Other Possible Causes

### 1) A node never reaches `END`

If your loop has no exit condition, the graph will keep cycling.

```python
# bad
builder.add_edge("check", "check")

Fix it with a terminating branch:

builder.add_conditional_edges(
    "check",
    should_continue,
    {"loop": "check", "stop": END},
)

2) Your conditional function returns an unexpected label

This happens when your router returns "done" but your mapping expects "finish".

def route(state):
    return "done"   # not mapped anywhere

builder.add_conditional_edges(
    "router",
    route,
    {"finish": END}
)

Keep router outputs and edge keys identical.

3) You’re mixing old and new LangGraph patterns

Older examples use different state handling than current releases. If you copied code from an outdated post, you may be using deprecated imports or patterns around MessageGraph, RunnableLambda, or agent executors that don’t match your installed version.

Check versions first:

pip show langgraph langchain-core langchain

Then align your code with the installed API.

4) A tool call waits forever

When using agents plus tools, one tool can hang on network I/O or an external dependency. The graph looks “stuck,” but the real issue is an unresolved tool invocation.

Typical symptoms include long pauses around nodes wrapping ToolNode or custom tool functions.

def fetch_policy_data(policy_id: str):
    response = requests.get(url, timeout=10)  # without timeout, this can hang
    return response.json()

Always set timeouts on external calls.

How to Debug It

  1. Print every node input and output

    • Add logging inside each node.
    • Verify every node returns a dict with the expected keys.
  2. Inspect routing labels

    • Log the exact string returned by conditional functions.
    • Compare it to the keys in add_conditional_edges(...).
  3. Run with streaming

    • Use graph.stream(...) to see where execution stops.
    • If it stops after one node, that node likely returned bad state or routed incorrectly.
  4. Check for hidden blocking calls

    • Look for requests.get(...), database queries, LLM calls, or file I/O without timeouts.
    • A “stuck” chain is often just waiting on an external dependency.

Prevention

  • Always make nodes return explicit partial state updates.
  • Keep routing labels centralized as constants so they don’t drift.
  • Put timeouts on every external API call inside tools and nodes.
  • Add a small integration test that runs the full graph from entry point to END.

If you want one rule to remember: in LangGraph, every node must either update state correctly or terminate cleanly. Anything else eventually turns into a stuck execution.


Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides