## TL;DR
Two parallel nodes wrote to the `messages` channel in the same super-step, and without a reducer that merges lists, LangGraph rejects the second write. Fix it by declaring `messages` with an append reducer (`Annotated[list, add_messages]`), or by routing the parallel branches through a single writer node.

```text
langgraph "At key 'messages': Can receive only one value per step"
```

## Use this when
- Parallel branches in a LangGraph run fail with this message
- Fan-out nodes each append to messages
- An agent fans out tool calls that all write messages

## Not for this skill when
- Updates arent dicts at all (thats the invalid-update error)
- The graph loops (thats the recursion limit)
- A single node writes twice (restructure the node)

## Steps

1. Confirm the collision: the error names the key and the step. Check which nodes ran in parallel:

```python
for step in app.stream(input, stream_mode="updates"):
    print(list(step.keys()))
```
Expected output: two node names in the same step dict. Those are the writers colliding on `messages`.

2. Declare the messages channel with a merging reducer:

```python
from typing import Annotated
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]
```
Expected output: parallel writes now merge instead of colliding. `add_messages` appends; without the annotation the channel accepts exactly one write per step.

3. If only one branch should own messages, serialize the writes instead:

```python
# route branch_a, then collector, then branch_b, with collector merging results
```
Expected output: the graph runs the writers in sequence and the error disappears. Use this when merge order matters and parallel appends would interleave unpredictably.

4. For fan-out tool patterns, collect tool results in a separate channel and write messages once:

```python
class State(TypedDict):
    messages: Annotated[list, add_messages]
    tool_results: Annotated[list, operator.add]
```
Expected output: parallel nodes write `tool_results`, one downstream node formats them into `messages`. No collision, deterministic ordering.

## Variant phrasings

### same error on a custom key, not messages
That key also needs a reducer. Any channel written by parallel nodes needs one; `operator.add` works for lists.

### error appears only with certain inputs
Only some inputs trigger the parallel path. The fix is the same; the input just decides whether the collision happens.

## Why it happens
LangGraph executes parallel branches in the same super-step, and each channel accepts one write per step unless a reducer defines how to combine multiple. `messages` without `add_messages` is single-write, so the second parallel write is rejected. The default tutorials use the annotated form, but hand-built or agent-built schemas often declare a bare list and only fail when the graph actually fans out.

## Edge cases
- `add_messages` dedupes by message id; parallel branches emitting identical ids will silently merge, which can look like lost messages.
- Reducers run in branch completion order, not definition order; dont rely on ordering across parallel writes.
- Conditional edges that skip one branch avoid the collision, which is why the error is input-dependent.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_uyyu5Ygw0i_Fdms4ZkCoXQ
