langgraph "InvalidUpdateError: At key 'messages': Can receive only one value per step"
Fixes LangGraph's InvalidUpdateError on the messages key, which fires when one graph step writes two values to a channel with no reducer. Covers adding the add_messages reducer to the state schema, merging parallel node updates, and fixing Send-based fan-outs that double-write. Use when the error names a key and refuses a second value in one step.
TL;DR
This error means one graph step tried to write two separate values to the messages key, and that key has no reducer to combine them. Add add_messages as the reducer on the messages field of your state, or restructure the step so it emits a single update. The fix is usually one line in the state schema.
Error
InvalidUpdateError: At key 'messages': Can receive only one value per stepSteps
- Find which node runs in the failing step. Stream the graph in debug mode and watch the node names:
for chunk in graph.stream(inputs, stream_mode="debug"):
print(chunk)Expected output: the debug stream shows the node name for the step that raises. That node is writing to messages twice, or two parallel nodes are both writing to it in the same step.
- Check the state schema for a reducer on
messages:
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
class State(TypedDict):
messages: Annotated[list, add_messages]Expected output: with the reducer present, parallel writes merge instead of colliding. If your schema is a plain TypedDict with messages: list and no Annotated, that missing reducer is the bug.
- If two parallel branches both return messages, have each return one list and let the reducer merge them:
def branch_a(state):
return {"messages": [AIMessage(content="a")]}
def branch_b(state):
return {"messages": [AIMessage(content="b")]}Expected output: both messages land in state, no InvalidUpdateError. Without the reducer, the second write in the same step is what raises.
- If the step uses
Send()for fan-out, return the Send objects from a single node and keep the state update in that one return:
def router(state):
return [Send("worker", {"text": state["text"]}) for _ in range(3)]Expected output: fan-out runs without touching the parent step's messages key, so there is no second write to collide with.
- Re-run the graph and confirm the step completes and the final state holds the merged messages.
Expected output: the stream finishes, and the last state shows messages from every branch in order.
When to use
- The traceback names a key like
messagesand says only one value per step is allowed - The error appears right after adding a parallel branch or a new node
- A node returns a dict that writes the same key twice, or two nodes share one step
When not to use
- The error names a different exception type (read the actual message first)
- The graph fails at compile time (that is a wiring problem, not a step-write problem)
- State updates succeed but values look wrong (that is reducer logic, not this error)
Variant phrasings
InvalidUpdateError at key 'count' or another custom key
Same cause on a non-message channel. Add a reducer for that key (operator.add works for numbers) or write it once per step.
error only appears with parallel nodes, fine in serial
Parallel nodes share a step. In serial runs each node gets its own step, so the collision never happens. The reducer is still the fix.
Why it happens
LangGraph channels accept one write per step unless a reducer is attached. The messages key is special because nearly every node writes to it, so the moment two nodes share a step (parallel branches, or a node that returns two updates for the same key), the channel sees two values and raises. add_messages exists exactly to merge those writes.
Edge cases
- Returning
{"messages": msg}with a single message object instead of a list can also collide; always return a list. - A node that calls
Send()AND returns a messages update in the same step writes twice; split them into separate nodes. - Custom reducers must be pure functions; a reducer with side effects produces nondeterministic merged state.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_rhxU3Z7c93WFQ9B28ZX1tQ