You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
update_state with Overwrite on a plain key persists the Overwrite wrapper instead of its payload (LastValue / LastValueAfterFinish / UntrackedValue) #9019
LangGraph main at 230927fb3a9ac9b2893a30322b4dfea7cdea9a8f
A replace-style state update (update_state with an Overwrite, i.e. the form used for replace-writes through the API server and by clients that wrap replace-style updates in Overwrite) on a plain (non-annotated) state key persists the Overwrite wrapper object into the checkpointed state instead of its payload. get_state().values then returns the wrapper, and any downstream consumer of the state fails (e.g. TypeError: 'Overwrite' object is not subscriptable).
Root cause: plain keys compile to LastValue, and LastValue.update (also LastValueAfterFinish.update and UntrackedValue.update) assigns values[-1] verbatim without the _get_overwrite check. BinaryOperatorAggregate (on its seeded path) and DeltaChannel both unwrap an Overwrite write, so the single-value channels are the inconsistent ones.
No error is raised at update time; the wrapper silently persists into state and surfaces at the consumer, e.g. TypeError: 'Overwrite' object is not subscriptable.
I'd like to fix this. The root cause is clear: LastValue.update, LastValueAfterFinish.update, and UntrackedValue.update assign values[-1] verbatim without running it through _get_overwrite, unlike BinaryOperatorAggregate (on its seeded path) and DeltaChannel, which already unwrap an Overwrite write. The fix is to unwrap through _get_overwrite in those three single-value channels, so a replace-style update_state persists the payload (e.g. "replaced") instead of the Overwrite wrapper. I'll add channel-level regression tests for all three plus an update_state end-to-end test.
The fix is ready: unwrap Overwrite writes in LastValue.update, LastValueAfterFinish.update, and UntrackedValue.update via _get_overwrite (mirroring BinaryOperatorAggregate / DeltaChannel), with regression tests covering overwrite-on-plain-key with LastValue, LastValueAfterFinish, and UntrackedValue. All tests pass locally. May I get this assigned so I can open the PR?
PR opened: #9032 — LastValue / LastValueAfterFinish / UntrackedValue now unwrap an Overwrite write through _get_overwrite (mirroring BinaryOperatorAggregate and DeltaChannel), with regression tests for all three channels. Happy to adjust after review.
cc maintainers (LangChain (@langchain-ai)) - could one of you assign me this issue? PR #9032 was auto-closed because external contributors need to be assigned first. The fix (unwrap Overwrite writes in the single-value channels + regression tests) is ready; once assigned the PR will be reopened automatically.
Sydney Runkle (@sydney-runkle) - could you assign me this issue (or another maintainer)? My PR #9032 (LastValue/LastValueAfterFinish/UntrackedValue unwrap Overwrite writes via _get_overwrite, with regression tests) was auto-closed pending assignment; once assigned the bot will reopen it automatically. Happy to adjust anything after review.
Confirmed on current main (3cd57c0): LastValue, LastValueAfterFinish and UntrackedValue all store an Overwrite write verbatim, and the repro persists the wrapper. Unwrapping with _get_overwrite, as BinaryOperatorAggregate and DeltaChannel do, fixes it without other test changes in the langgraph suite.
Rain. (@Rainmemery) has this in #9032 (auto-closed only for the missing assignment), so assigning them seems simplest. One thing worth a maintainer's view: _get_overwrite also unwraps the {"__overwrite__": v} and {"type": "__overwrite__", "value": v} dict forms, so a plain key that stores exactly one of those dicts would now be unwrapped. That matches reducer channels, but it's a small behaviour change.
Activity
Rainmemery commentedon Sep 21, 2026
I'd like to fix this. The root cause is clear:
LastValue.update,LastValueAfterFinish.update, andUntrackedValue.updateassignvalues[-1]verbatim without running it through_get_overwrite, unlikeBinaryOperatorAggregate(on its seeded path) andDeltaChannel, which already unwrap anOverwritewrite. The fix is to unwrap through_get_overwritein those three single-value channels, so a replace-styleupdate_statepersists the payload (e.g."replaced") instead of theOverwritewrapper. I'll add channel-level regression tests for all three plus anupdate_stateend-to-end test.Could you assign me to #9019? Thanks!
Rainmemery commentedon Sep 21, 2026
The fix is ready: unwrap
Overwritewrites inLastValue.update,LastValueAfterFinish.update, andUntrackedValue.updatevia_get_overwrite(mirroringBinaryOperatorAggregate/DeltaChannel), with regression tests covering overwrite-on-plain-key withLastValue,LastValueAfterFinish, andUntrackedValue. All tests pass locally. May I get this assigned so I can open the PR?Rainmemery commentedon Sep 21, 2026
PR opened: #9032 —
LastValue/LastValueAfterFinish/UntrackedValuenow unwrap anOverwritewrite through_get_overwrite(mirroringBinaryOperatorAggregateandDeltaChannel), with regression tests for all three channels. Happy to adjust after review.Rainmemery commentedon Sep 21, 2026
cc maintainers (LangChain (@langchain-ai)) - could one of you assign me this issue? PR #9032 was auto-closed because external contributors need to be assigned first. The fix (unwrap Overwrite writes in the single-value channels + regression tests) is ready; once assigned the PR will be reopened automatically.
Rainmemery commentedon Sep 21, 2026
Sydney Runkle (@sydney-runkle) - could you assign me this issue (or another maintainer)? My PR #9032 (LastValue/LastValueAfterFinish/UntrackedValue unwrap Overwrite writes via _get_overwrite, with regression tests) was auto-closed pending assignment; once assigned the bot will reopen it automatically. Happy to adjust anything after review.
Iyanuoluwa007 commentedon Oct 21, 2026
Confirmed on current main (
3cd57c0):LastValue,LastValueAfterFinishandUntrackedValueall store anOverwritewrite verbatim, and the repro persists the wrapper. Unwrapping with_get_overwrite, asBinaryOperatorAggregateandDeltaChanneldo, fixes it without other test changes in the langgraph suite.Rain. (@Rainmemery) has this in #9032 (auto-closed only for the missing assignment), so assigning them seems simplest. One thing worth a maintainer's view:
_get_overwritealso unwraps the{"__overwrite__": v}and{"type": "__overwrite__", "value": v}dict forms, so a plain key that stores exactly one of those dicts would now be unwrapped. That matches reducer channels, but it's a small behaviour change.