TL;DR
- LangGraph raises InvalidUpdateError when multiple concurrent nodes write to the same state key in a single step without an aggregation rule.
- To intentionally combine concurrent updates, define the state key using typing.Annotated and a reducer function such as operator.add.
- Unintentional collisions can be prevented by assigning dedicated state keys to parallel branches or routing them into downstream fan-in nodes.
Diagnosing InvalidUpdateError and the INVALID_CONCURRENT_GRAPH_UPDATE Error
In LangGraph, conflicting writes during graph execution surface as the API class langgraph.errors.InvalidUpdateError. In LangGraph’s documentation, this issue is tracked under the standard error code INVALID_CONCURRENT_GRAPH_UPDATE.
The runtime throws this error when multiple parallel nodes (such as Fan-out branches executing concurrently in the same super-step) return an update dictionary targeting the exact same state key. Because LangGraph cannot determine how to resolve the conflicting incoming values for a channel designed to handle only a single write per step, it halts execution to prevent silent data loss or non-deterministic overwrites.
The exception produces the following message and troubleshooting reference:
langgraph.errors.InvalidUpdateError: At key '<key>': Can receive only one value per step. Use an Annotated key to handle multiple values.
For troubleshooting, visit: https://python.langchain.com/docs/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE
(Note: Runtime exceptions may print the legacy troubleshooting URL python.langchain.com/..., which corresponds to the current documentation hosted under docs.langchain.com/....)
This traceback indicates that two or more concurrently executing nodes attempted to write to <key> within the exact same super-step without an aggregation rule.
When diagnosing this error, confirm whether the concurrent writes to the same key are intentional or the result of graph design:
- Unintentional Key Collisions: If parallel branches produce distinct results that should not be combined into the same field, alter the graph structure to use dedicated state keys (such as
branch_a_outputandbranch_b_output). Alternatively, route edges from the parallel nodes into a downstream convergence or fan-in node that consumes both results and applies the unified update in a subsequent step. - Intentional Multi-Branch Updates: If multiple parallel branches must write to the same collection or field simultaneously, the target key must be declared using
typing.Annotatedwith an appropriate Reducer function to dictate how the concurrent values should be combined.
Sources: INVALID_CONCURRENT_GRAPH_UPDATE – Docs by LangChain, InvalidUpdateError, Errors – Docs by LangChain, GitHub Issue Title: InvalidUpdateError when using parallel subgraphs with shared state keys in LangGraph · Iss, How to modify specific item in a State – LangGraph – LangChain Forum, docs(langgraph): fixes fan-out example
The Pregel Execution Model: Why Concurrent Updates Halt the Tick Loop
To understand why this failure occurs under the hood, it helps to examine how the Pregel execution model processes state within a super-step. Specifically, an INVALID_CONCURRENT_GRAPH_UPDATE error arises when a StateGraph receives concurrent updates to a state property that does not support them, such as during a Fan-out or other parallel execution where multiple nodes return values for the same key within a single step. While a single node returning a value overwrites that key in the state, multiple nodes returning values for the same key create uncertainty about how to update the internal state, causing the graph to raise an error.
For example, executing a graph via graph.stream() under these conditions can result in a langgraph.errors.InvalidUpdateError:
langgraph.errors.InvalidUpdateError: At key 'jokes': Can receive only one value per step. Use an Annotated key to handle multiple values.
For troubleshooting, visit: https://python.langchain.com/docs/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE
To prevent or resolve this error when executing nodes in parallel, relevant state keys can be defined with a Reducer function using typing.Annotated (such as Annotated[list, operator.add]), which specifies the logic for combining multiple values.
Sources: INVALID_CONCURRENT_GRAPH_UPDATE – Docs by LangChain, Fix to graph-api docs Send API example
Default Overwrite Channels vs. Reducer Channels
This distinction highlights the core operational difference between default overwrite channels and reducer-enabled channels. By default, fields defined without an annotation in a TypedDict, dataclass, or Pydantic model operate as single-value channels backed internally by LastValue. When such a channel is configured without a Reducer function, receiving multiple update values in a single step causes LangGraph to raise an InvalidUpdateError:
langgraph.errors.InvalidUpdateError: At key '<key>': Can receive only one value per step. Use an Annotated key to handle multiple values.
To handle multiple values, fields can be configured with Reducer functions using typing.Annotated (such as Annotated[int, operator.add]).
The presence or absence of a Reducer function also affects how bugs surface during execution. In one reported open issue (#9005), a developer observed a framework bug where overriding an inherited field in a dataclass or Pydantic subclass caused Command(update=...) to apply the update twice in a single step. When the overridden field (count) was configured without a Reducer function, this duplicate application triggered:
langgraph.errors.InvalidUpdateError: At key 'count': Can receive only one value per step. Use an Annotated key to handle multiple values.
However, when the field configured a Reducer function such as Annotated[int, operator.add], no error was raised, and the framework bug caused the reducer to apply the update twice (yielding a count of 5 instead of 3 from an initial value of 1 and an update of 2). Here, the reducer is not the source of the issue, but its normal aggregation behavior masks the framework’s double-invocation bug.
Sources: `Command(update=…)` applies an overridden inherited state field twice, Receiving multiple inputs on a single node not working in a vanilla state
Configuring Annotated State Keys with operator.add and Custom Reducers
Building on this reducer mechanism, state schemas in LangGraph can use typing.Annotated within a TypedDict to specify a standard Reducer function such as operator.add:
from typing import Annotated, Sequence
import operator
from typing_extensions import TypedDict
class AgentState(TypedDict):
messages: Annotated[Sequence[dict], operator.add]
class State(TypedDict):
my_key: Annotated[str, operator.add]
When multiple parallel upstream nodes (such as up and side in a topology like graph.add_edge(["up", "side"], "down")) execute concurrently in the same super-step and both write updates to the same state key, a reducer is required on that key to prevent update conflicts across those parallel branches before control reaches the downstream down node.
When list merging requires avoiding duplicate entries, custom reducer functions can be defined. For example, in a discussion around handling subgraph outputs, a community workaround demonstrated a custom reducer:
def reduce_results(left: list[str], right: list[str]) -> list[str]:
"""This approach might not be the best, but it can solve repeat problems."""
left_table = {result: i for i, result in enumerate(left)}
merged = left.copy()
for right_result in right:
if (existing_idx := left_table.get(right_result)) is not None:
merged[existing_idx] = right_result
else:
merged.append(right_result)
return merged
class ParentState(TypedDict):
question: str
results: Annotated[list[str], reduce_results]
In that same discussion, a contributor suggested separating input and output schemas when handling subgraphs: using the parent graph’s state for the input allows full access to parent state values, while the output schema contains only the required fields, typed as lists with an Annotated Reducer function.
Sources: Sink node issue, if multiple subgraphs are used in parallel, two node flow to one node
Edge Cases: Parallel Subgraphs, Conditional Self-Loops, and Shared State Keys
Beyond standard fan-out configurations, several distinct edge cases and open bug reports illustrate scenarios where InvalidUpdateError surfaces unexpectedly. These scenarios stem from different factors and should not be conflated:
In an open bug report (issue #6446), executing a compiled subgraph in parallel with another node in the parent graph resulted in an unexpected InvalidUpdateError. In the reporter’s minimal reproducer, a parent graph branched from an initial node to both a standard node (nodeb) and a compiled subgraph:
def create_graph():
builder = StateGraph(State)
builder.add_node("nodea", nodea)
builder.add_node("subgraph", subgraph())
builder.add_node("nodeb", nodeb)
builder.add_edge(START, "nodea")
builder.add_edge("nodea", "nodeb")
builder.add_edge("nodea", "subgraph")
builder.add_edge("nodeb", END)
builder.add_edge("subgraph", END)
return builder.compile()
When nodeb and subgraph executed concurrently, LangGraph raised:
langgraph.errors.InvalidUpdateError: At key 'b': Can receive only one value per step. Use an Annotated key to handle multiple values.
The reporter noted that nodeb only updated key b while the subgraph internal nodes only updated keys s1 and s2. However, the root cause has not been officially confirmed by maintainers, and the report remains an open bug rather than an established graph behavior.
In another scenario (issue #2336), a user encountered an update collision when combining parallel branches with a conditional self-loop (not a RetryPolicy). An initial node branched into parallel nodes b and c, where node c evaluated a conditional edge that routed back to itself on a retry condition before proceeding downstream. Executing this flow produced InvalidUpdateError targeting key count_b. Because the issue was closed without an official explanation or confirmed fix, it is only presumed that a self-loop iteration coincided with another branch in the same super-step to cause multiple writes to count_b.
Sources: GitHub Issue Title: InvalidUpdateError when using parallel subgraphs with shared state keys in LangGraph, langgraph.errors.InvalidUpdateError: Can receive only one value per step. Use an Annotated key to handle multiple values. error when running retry in graph parallel processing
Closing thoughts
In standard graph workflows, InvalidUpdateError serves as an essential safeguard within LangGraph’s Pregel model, preventing non-deterministic data overwrites when multiple concurrent nodes write to the same unannotated channel. When parallel writes to a shared key are intentional, configuring the field with an Annotated reducer provides a deterministic aggregation rule. For unintentional collisions, architectural adjustments such as dedicated state keys or downstream fan-in nodes cleanly separate updates across steps.
However, reducers should not be treated as a blanket remedy. In specific bug paths such as subclass field inheritance with Command(update=...) (#9005), adding a reducer can mask duplicate updates that would otherwise trigger a visible error. Furthermore, edge cases like parallel subgraphs (#6446) and conditional self-loops (#2336) represent distinct issues with separate, unconfirmed causes rather than straightforward topology mistakes. Distinguishing between intended concurrent updates, graph architectural patterns, and unresolved framework bugs is crucial when troubleshooting update conflicts in LangGraph.
Frequently Asked Questions
Why does LangGraph raise an InvalidUpdateError?
LangGraph raises this error when multiple parallel nodes attempt to update the exact same state key within the same super-step without an aggregation rule. It halts execution to prevent non-deterministic overwrites and silent data loss.
How can you resolve InvalidUpdateError when multiple branches intentionally update the same key?
You can declare the target state key using typing.Annotated along with a Reducer function, such as operator.add or a custom reducer. This tells LangGraph how to combine the conflicting concurrent values instead of attempting an overwrite.
What should you do if parallel nodes are causing unintentional key collisions?
You can alter the graph structure to give each branch its own dedicated state key, such as branch_a_output and branch_b_output. Alternatively, you can route the parallel nodes to a downstream fan-in node that aggregates the values in a subsequent step.
Keep reading: LLM Agents & Tooling
- Troubleshooting CrewAI ValidationError: Please Provide an OpenAI API Key — Fix CrewAI ValidationError: Please provide an OpenAI API key when setting memory=True by providing an OPENAI_API_KEY or configuring a custom embedder.
- Troubleshooting ChromaDB InvalidDimensionException: Resolving the 1536 vs 384 Dimension Mismatch — Fix ChromaDB InvalidDimensionException where embedding dimension 1536 does not match collection dimensionality 384 by explicitly setting the…
- Troubleshooting LangChain OutputParserException: Both Final Answer and Parse-able Action Produced — Resolve LangChain’s OutputParserException caused when a ReAct agent outputs both an action and final answer. Learn the root causes and best fixes.
- Resolving Pydantic State Deserialization Failures in LangGraph Checkpointers — Pydantic state turning into a raw dict in LangGraph checkpoints? Four causes: msgpack allowlist, unimportable classes, schema drift, unserializable…
All guides in this series: LLM Agent Guides: LangChain, LangGraph & AI Tooling

