How to Fix 'state not updating in production' in LangGraph (TypeScript)
If your LangGraph state updates work locally but stop changing in production, the issue is usually not LangGraph itself. It usually means your graph is mutating state in a way that looks fine in-memory, but fails once the app is deployed, restarted, or run across multiple requests.
In TypeScript, this often shows up as stale values, missing reducer merges, or nodes returning partial state in a way that never gets persisted. The result is usually something like state not updating, missing required key, or graph behavior that looks nondeterministic under load.
The Most Common Cause
The #1 cause is mutating state directly instead of returning a new partial update that LangGraph can merge through the reducer.
LangGraph state should be treated as immutable. If you do state.messages.push(...) or assign into nested objects and return the same reference, you can get behavior that appears to work in local tests but breaks when the runtime reuses objects differently in production.
| Broken pattern | Correct pattern |
|---|---|
Mutates state directly | Returns a new partial update |
| Relies on object identity | Uses reducer-friendly updates |
| Works by accident in dev | Survives production execution |
import { StateGraph, Annotation } from "@langchain/langgraph";
const GraphState = Annotation.Root({
messages: Annotation<string[]>({
reducer: (left, right) => left.concat(right),
default: () => [],
}),
});
type State = typeof GraphState.State;
const badNode = async (state: State) => {
// ❌ Wrong: mutating existing array
state.messages.push("new message");
// ❌ Wrong: returning same object reference
return state;
};
const goodNode = async (state: State) => {
// ✅ Right: return a partial update
return {
messages: ["new message"],
};
};
The fixed version works because LangGraph merges node outputs into the graph state using the reducer you defined. The broken version depends on side effects, and side effects are exactly what break when execution changes between local and production environments.
If you are seeing errors like:
- •
InvalidUpdateError: Expected node to return an object with at least one key - •
State key "messages" was not updated - •
Cannot read properties of undefinedafter a later node
then this is usually where to look first.
Other Possible Causes
1. Missing reducer for fields that are updated multiple times
If multiple nodes write to the same key and you did not define a reducer, later updates can overwrite earlier ones or fail validation.
const GraphState = Annotation.Root({
messages: Annotation<string[]>({
// ✅ needed for append-style updates
reducer: (left, right) => left.concat(right),
default: () => [],
}),
});
Without this, one node may replace the array entirely instead of appending to it.
2. Returning the wrong shape from a node
A LangGraph node must return a plain object keyed by state fields. Returning an array, string, or nested wrapper object causes silent confusion during debugging and often throws runtime validation errors.
// ❌ Wrong
const badNode = async () => {
return ["message"];
};
// ✅ Right
const goodNode = async () => {
return { messages: ["message"] };
};
If you are using StateGraph, make sure your returned keys match the annotation exactly.
3. Mixing up ephemeral local variables with graph state
A common production bug is storing important data in local variables inside a node instead of putting it into graph state. That works inside one function call, then disappears on the next step.
const node = async (state: State) => {
const token = "abc123"; // ❌ local only
return {
messages: [`token=${token}`], // ✅ if you need it later, store it in state
};
};
If another node needs that value later, it must be part of the graph state or persisted externally.
4. Using an old compiled graph after changing schema
In TypeScript projects with hot reload or long-lived processes, it is easy to deploy code with an updated schema while still running an old compiled graph instance. That leads to weird issues like missing keys or nodes reading stale shapes.
// Make sure this is recreated after schema changes
const graph = builder.compile();
If your runtime caches modules aggressively, restart the process after changing annotations or reducers.
How to Debug It
- •
Inspect the exact error text
- •If you see
InvalidUpdateError, focus on what each node returns. - •If you see missing keys later in execution, focus on reducers and return shape.
- •If there is no exception but values stay stale, suspect mutation or caching.
- •If you see
- •
Log every node input and output
- •Add logs before and after each node.
- •Confirm whether the output object contains only changed keys.
- •Check whether arrays/objects are being mutated in place.
- •
Verify your annotations
- •Every field updated more than once needs a reducer.
- •Every field used by downstream nodes needs a stable type and default.
- •Compare your runtime state shape against
Annotation.Root(...).
- •
Run production-like execution locally
- •Disable hot reload.
- •Recreate the graph per process startup.
- •Use the same Node version and deployment settings as prod.
A useful trick is to print serialized state snapshots:
console.log("input", JSON.stringify(state));
console.log("output", JSON.stringify(result));
If input changes but output does not contain the expected key, your node logic is wrong. If output looks correct but downstream nodes still see stale data, your reducer or graph wiring is wrong.
Prevention
- •Treat LangGraph state as immutable.
- •Return partial updates only; never mutate nested objects in place.
- •Define reducers for any field that can be written by more than one node.
- •Rebuild and redeploy after schema changes; do not trust hot reload for graph structure changes.
- •Add tests that assert final merged state, not just single-node outputs.
If you keep seeing state not updating in production, start with mutation first. In LangGraph TypeScript apps, that is the most common reason local behavior diverges from deployed behavior.
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