How to Fix 'state not updating' in LangGraph (TypeScript)
When LangGraph says “state not updating”, it usually means your node ran, but the graph never persisted the change into the next state snapshot. In TypeScript, this almost always shows up when you return the wrong shape from a node, mutate state in place, or define your schema in a way LangGraph can’t merge correctly.
The failure mode is predictable: the node executes, logs look fine, but downstream nodes still see the old value. That makes it look like LangGraph is ignoring your update when the real issue is that your return value does not match what StateGraph expects.
The Most Common Cause
The #1 cause is returning a full object or mutating state directly instead of returning a partial state update.
In LangGraph, node functions should return only the fields you want to update. If you mutate state and return it, or return a nested object that doesn’t match the schema, the update may be dropped or merged incorrectly.
Broken vs fixed
| Broken pattern | Correct pattern |
|---|---|
| Mutates state in place | Returns a partial update object |
| Returns the whole state object | Returns only changed keys |
| Uses arrays/objects without proper reducer | Uses reducer-aware schema |
import { StateGraph, Annotation } from "@langchain/langgraph";
const GraphState = Annotation.Root({
messages: Annotation<string[]>({
reducer: (left, right) => left.concat(right),
default: () => [],
}),
status: Annotation<string>({
default: () => "idle",
}),
});
type State = typeof GraphState.State;
// ❌ Broken
const brokenNode = async (state: State) => {
state.status = "done"; // mutation
state.messages.push("processed"); // mutation
return state; // returns mutated full object
};
// ✅ Fixed
const fixedNode = async (_state: State) => {
return {
status: "done",
messages: ["processed"],
};
};
const graph = new StateGraph(GraphState)
.addNode("worker", fixedNode)
.addEdge("__start__", "worker")
.addEdge("worker", "__end__")
.compile();
If you are using Annotation, remember this rule:
- •Return partial updates
- •Do not mutate input state
- •Use reducers for append/merge behavior
For example, if messages should accumulate, it needs a reducer. If you just return messages: ["processed"] without one, later updates can overwrite earlier ones.
Other Possible Causes
1. Your schema has no reducer for an accumulating field
If you want to append to an array but didn’t define a reducer, LangGraph will replace instead of merge.
// ❌ Broken
const GraphState = Annotation.Root({
messages: Annotation<string[]>({
default: () => [],
}),
});
// ✅ Fixed
const GraphState = Annotation.Root({
messages: Annotation<string[]>({
reducer: (left, right) => left.concat(right),
default: () => [],
}),
});
If you see behavior like “only the last message survives,” this is usually why.
2. You returned the wrong key name
LangGraph only updates keys that exist in the schema. If your node returns message but the schema expects messages, nothing useful gets persisted.
// ❌ Broken
return { message: ["hello"] };
// ✅ Fixed
return { messages: ["hello"] };
This one is easy to miss because TypeScript won’t always catch it if your node is loosely typed.
3. You are using a nested object where LangGraph expects flat fields
A common mistake is returning { data: { status: "done" } } when your state definition is { status }.
// ❌ Broken
return {
data: {
status: "done",
},
};
// ✅ Fixed
return {
status: "done",
};
Unless your schema explicitly defines data, that update will not land where you think it will.
4. Your node returns undefined on some path
If one branch forgets to return an update object, LangGraph has nothing to apply.
// ❌ Broken
const node = async (state: State) => {
if (!state.messages.length) {
console.log("no messages");
return; // undefined
}
return { status: "done" };
};
// ✅ Fixed
const node = async (state: State) => {
if (!state.messages.length) {
return { status: "empty" };
}
return { status: "done" };
};
In practice this shows up as intermittent “state not updating” depending on which branch runs.
How to Debug It
- •
Log the exact node output
- •Add a log right before
return. - •Confirm you are returning a plain object with valid keys.
- •Example:
console.log("node output:", output);
- •Add a log right before
- •
Check the compiled state type
- •Make sure your node return type matches
typeof GraphState.State. - •If you’re using
any, stop doing that here. - •Type mismatches often hide bad key names and nested shapes.
- •Make sure your node return type matches
- •
Inspect reducers on every accumulating field
- •Arrays and objects that should merge need explicit reducers.
- •If updates overwrite each other, the issue is usually schema-level, not execution-level.
- •
Run a minimal graph with one node
- •Remove conditional branches and extra edges.
- •Use one input, one node, one output.
- •If the update works there, your bug is in routing or branching logic rather than persistence.
Prevention
- •Return partial updates only from nodes.
- •Define reducers for any field that should accumulate across steps.
- •Keep your state schema flat unless you have a strong reason not to.
- •Use strict TypeScript types for node inputs and outputs instead of
any.
If you keep seeing state not updating, read it as a contract violation between your node output and your Annotation.Root schema. In LangGraph TypeScript apps, that contract matters more than whether the function “ran successfully.”
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