langchain-ai / langchain-ai/langgraph
BinaryOperatorAggregate: Overwrite not unwrapped and duplicate-Overwrite guard skipped when initial value is MISSING
- Dominant language
- Python
- Stars
- 41.8k
- Forks
- 7.1k
- Avg merge
- 23h 7m
- Merged PRs (30d)
- 30
Description
### Checked other resources
- [x] This is a bug, not a usage question.
- [x] I added a clear and descriptive title that summarizes this issue.
- [x] I used the GitHub search to find a similar question and didn't find it.
- [x] I am sure that this is a bug in LangGraph rather than my code.
- [x] The bug is not resolved by updating to the latest stable version of LangGraph (or the specific integration package).
- [x] This is not related to the langchain-community package.
- [x] I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.
### Reproduction Steps / Example Code (Python)
```python
import operator
from dataclasses import dataclass
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START
from langgraph.types import Overwrite
from langgraph.channels.binop import BinaryOperatorAggregate
from langgraph.errors import InvalidUpdateError
@dataclass
class Metrics:
count: int
def __add__(self, other: "Metrics") -> "Metrics":
return Metrics(self.count + other.count)
# Bug 1: Overwrite wrapper stored instead of unwrapped value when channel starts MISSING
class State(TypedDict):
metrics: Annotated[Metrics, operator.add]
def reset_node(state: State):
return {"metrics": Overwrite(Metrics(count=42))}
graph = (
StateGraph(State)
.add_node("reset", reset_node)
.add_edge(START, "reset")
.compile()
)
print(graph.invoke({}))
# Expected: {'metrics': Metrics(count=42)}
# Actual: {'metrics': Overwrite(value=Metrics(count=42))}
# Bug 2: Duplicate Overwrite guard not enforced when first update is on MISSING channel
channel = BinaryOperatorAggregate(Metrics, operator.add)
try:
channel.update([Overwrite(Metrics(1)), Overwrite(Metrics(2))])
print("NO ERROR (bug):", channel.get())
except InvalidUpdateError as e:
print("Expected error:", e)
```
### Error Message and Stack Trace (if applicable)
```shell
No exception is raised. The incorrect output is:
{'metrics': Overwrite(value=Metrics(count=42))}
Metrics(count=2)
```
### Description
When using `BinaryOperatorAggregate` with a type that has no default constructor, the channel sets `self.value` to
`MISSING`. In this case, passing an `Overwrite(x)` value to `update()` stores the wrapper object instead of the
unwrapped payload, so `channel.get()` returns `Overwrite(value=x)` instead of `x`.
On top of that, the `seen_overwrite` guard is never set in this code path, so passing two `Overwrite` values in the
same super-step silently succeeds instead of raising `InvalidUpdateError` as expected.
Both issues happen because the `MISSING` fast-path in `BinaryOperatorAggregate.update` skips the `_get_overwrite`
check that runs for all other values.
### System Info
System Information
------------------
> OS: Linux
> OS Version: #1 SMP Mon Feb 2 12:27:57 UTC 2026
> Python Version: 3.12.12 (main, Oct 10 2025, 08:52:57) [GCC 11.4.0]
Package Information
-------------------
> langchain_core: 1.2.13
> langchain: 1.2.10
> langsmith: 0.7.3
> langgraph_sdk: 0.3.6
Optional packages not installed
-------------------------------
> langserve
Other Dependencies
------------------
> httpx: 0.28.1
> jsonpatch: 1.33
> langgraph: 1.0.8
> opentelemetry-api: 1.38.0
> opentelemetry-exporter-otlp-proto-http: 1.38.0
> opentelemetry-sdk: 1.38.0
> orjson: 3.11.7
> packaging: 26.0
> pydantic: 2.12.3
> pytest: 8.4.2
> pyyaml: 6.0.3
> requests: 2.32.4
> requests-toolbelt: 1.0.0
> rich: 13.9.4
> tenacity: 9.1.4
> typing-extensions: 4.15.0
> uuid-utils: 0.14.0
> xxhash: 3.6.0
> zstandard: 0.25.0
Contributor guide
Research direction
Start with BinaryOperatorAggregate.update and the _get_overwrite path described in the issue, using the provided reproduction for a channel whose initial value is MISSING. Verify that an Overwrite stores its payload and that two Overwrite updates raise InvalidUpdateError; done means both behaviors work for the shown examples.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100