How to Fix 'state not updating in production' in LangGraph (Python)
Opening
If your LangGraph app works locally but stops updating state in production, the problem is usually not LangGraph itself. It means your graph is writing to a state object that is either not persisted, not merged correctly, or being overwritten by a bad reducer or concurrency bug.
This usually shows up after deployment when you move from a single-process dev setup to multiple workers, async execution, or a real checkpointer like Postgres or Redis.
The Most Common Cause
The #1 cause is mutating state in place instead of returning a new partial update from the node.
LangGraph expects nodes to return a dict of updates keyed by state fields. If you mutate the existing state object and return nothing, the graph has nothing to persist.
Broken vs fixed pattern
| Broken pattern | Fixed pattern |
|---|---|
Mutates state in place | Returns a partial update dict |
| Works “sometimes” in local tests | Fails in production because updates are not checkpointed |
| Hard to debug because no exception is raised | Deterministic and checkpoint-friendly |
# BROKEN
from typing import TypedDict
from langgraph.graph import StateGraph, END
class State(TypedDict):
messages: list[str]
counter: int
def increment_counter(state: State):
state["counter"] += 1 # in-place mutation
# returns None -> LangGraph has no update to persist
graph = StateGraph(State)
graph.add_node("increment_counter", increment_counter)
graph.set_entry_point("increment_counter")
graph.add_edge("increment_counter", END)
app = graph.compile()
# FIXED
from typing import TypedDict
from langgraph.graph import StateGraph, END
class State(TypedDict):
messages: list[str]
counter: int
def increment_counter(state: State):
return {"counter": state["counter"] + 1}
graph = StateGraph(State)
graph.add_node("increment_counter", increment_counter)
graph.set_entry_point("increment_counter")
graph.add_edge("increment_counter", END)
app = graph.compile()
If you need to append to a list, return the new value or use a reducer designed for accumulation.
from typing_extensions import Annotated
from operator import add
from typing import TypedDict
class State(TypedDict):
messages: Annotated[list[str], add]
That reducer tells LangGraph how to merge concurrent updates instead of replacing the field blindly.
Other Possible Causes
1) No checkpointer in production
A very common production bug is compiling the graph without persistence. In-memory state disappears between requests, worker restarts, and replica hops.
# BAD: no persistence
app = graph.compile()
# GOOD: attach a checkpointer
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)
For real production traffic, use a durable backend like Postgres. MemorySaver is only for local testing.
2) Missing thread_id in config
LangGraph uses the thread ID to identify which conversation/state bucket to load and save. If you omit it, every request can look like a fresh run.
# BAD
result = app.invoke({"messages": ["hi"]})
# GOOD
result = app.invoke(
{"messages": ["hi"]},
config={"configurable": {"thread_id": "user-123"}}
)
If you are using RunnableConfig, make sure the configurable.thread_id value is stable per user/session.
3) Wrong state schema or missing reducers
If two nodes write to the same field and your schema does not define how to merge them, one update can overwrite the other. This often looks like “state not updating” when it is actually being replaced.
from typing import TypedDict
class State(TypedDict):
messages: list[str] # no reducer defined
Fix it with an annotated reducer:
from typing_extensions import Annotated
from operator import add
from typing import TypedDict
class State(TypedDict):
messages: Annotated[list[str], add]
Use reducers for append-only fields like messages, events, logs, or tool outputs.
4) Returning the wrong shape from the node
LangGraph nodes must return a mapping of updated fields. Returning a raw string, tuple, or nested object that does not match your schema will not update state correctly.
# BAD
def node(state):
return "done"
# GOOD
def node(state):
return {"status": "done"}
If you see errors like:
- •
InvalidUpdateError - •
Expected dict, got ... - •
State update must be a mapping
then this is probably your issue.
How to Debug It
- •
Print every node input and output
- •Add logging inside each node.
- •Confirm the function returns a dict with keys that exist in your state schema.
- •
Check whether you are using persistence
- •Verify
compile(checkpointer=...). - •If you are on multiple workers, confirm the same backend is used across all replicas.
- •Verify
- •
Verify your thread identity
- •Log
config["configurable"]["thread_id"]. - •Make sure it stays constant across requests for the same conversation.
- •Log
- •
Inspect reducers and concurrent writes
- •Look for fields updated by more than one node.
- •If two nodes write to
messages,events, or similar lists, add an explicit reducer likeoperator.add.
A quick test:
result = app.invoke(
{"messages": ["hello"], "counter": 0},
config={"configurable": {"thread_id": "debug-1"}}
)
print(result)
If this works once but not across calls with the same thread ID, your issue is almost always checkpointing or config identity.
Prevention
- •Always treat LangGraph state as immutable input/output data.
- •Return partial updates; do not mutate in place.
- •Use a durable checkpointer in production.
- •
MemorySaveris fine for local dev only.
- •
- •Define reducers for append-only fields.
- •Especially for message histories and event logs.
- •Standardize thread IDs early.
- •Tie them to user/session IDs and log them on every request.
If you build graphs this way from day one, “state not updating in production” stops being a mystery and becomes a quick config check.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
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