Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-langgraph for 
openSUSE:Factory checked in at 2026-10-27 16:58:19
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-langgraph (Old)
 and      /work/SRC/openSUSE:Factory/.python-langgraph.new.2252680 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-langgraph"

Tue Oct 27 16:58:19 2026 rev:13 rq:1392919 version:1.2.18

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-langgraph/python-langgraph.changes        
2026-10-26 10:04:32.855150510 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-langgraph.new.2252680/python-langgraph.changes
   2026-10-27 16:58:20.952069301 +0100
@@ -1,0 +2,12 @@
+Tue Oct 27 06:32:08 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to 1.2.18:
+  * Default delta-channel snapshots to every 100 updates
+    instead of 1000 to bound replay latency
+  * Let node error_handler recover on the executor path and
+    stream failed task results
+  * Stream the last checkpoint of a run stopped by
+    interrupt_after or the recursion limit
+  * Stop duplicating tags at every subgraph level
+
+-------------------------------------------------------------------

Old:
----
  langgraph-1.2.17.tar.gz

New:
----
  langgraph-1.2.18.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-langgraph.spec ++++++
--- /var/tmp/diff_new_pack.XFVeiP/_old  2026-10-27 16:58:22.428131077 +0100
+++ /var/tmp/diff_new_pack.XFVeiP/_new  2026-10-27 16:58:22.430131160 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           python-langgraph
-Version:        1.2.17
+Version:        1.2.18
 Release:        0
 Summary:        Library for building stateful, multi-actor applications with 
LLMs
 License:        MIT

++++++ langgraph-1.2.17.tar.gz -> langgraph-1.2.18.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.17/PKG-INFO 
new/langgraph-1.2.18/PKG-INFO
--- old/langgraph-1.2.17/PKG-INFO       2020-02-02 01:00:00.000000000 +0100
+++ new/langgraph-1.2.18/PKG-INFO       2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.5
 Name: langgraph
-Version: 1.2.17
+Version: 1.2.18
 Summary: Building stateful, multi-actor applications with LLMs
 Project-URL: Homepage, https://docs.langchain.com/oss/python/langgraph/overview
 Project-URL: Documentation, https://reference.langchain.com/python/langgraph/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.17/langgraph/_internal/_config.py 
new/langgraph-1.2.18/langgraph/_internal/_config.py
--- old/langgraph-1.2.17/langgraph/_internal/_config.py 2020-02-02 
01:00:00.000000000 +0100
+++ new/langgraph-1.2.18/langgraph/_internal/_config.py 2020-02-02 
01:00:00.000000000 +0100
@@ -347,6 +347,7 @@
         recursion_limit=DEFAULT_RECURSION_LIMIT,
         configurable={},
     )
+    inherited_tags: set[str] = set()
     if var_config := var_child_runnable_config.get():
         empty.update(
             {
@@ -355,6 +356,7 @@
                 if _is_not_empty(v)
             },
         )
+        inherited_tags.update(empty["tags"])
     # An explicit config that supplies its own checkpoint coordinate (a
     # thread_id, or any checkpoint_ns/checkpoint_id/checkpoint_map) is 
addressing
     # its own checkpoint lineage, so drop the inherited ambient configurable
@@ -407,12 +409,14 @@
                 elif k == "tags":
                     # Concatenate tags across configs so values bound via
                     # with_config(...) are preserved when later configs
-                    # supply additional tags. Matches merge_configs.
+                    # supply additional tags. Matches merge_configs. Tags the
+                    # ambient config already has are skipped: a subgraph gets
+                    # its parent's config both explicitly and as the ambient
+                    # one, so they would double at every nesting level.
                     existing_tags: list[str] | None = empty.get("tags")
+                    new_tags = [t for t in cast(list, v) if t not in 
inherited_tags]
                     empty["tags"] = (
-                        [*existing_tags, *cast(list, v)]
-                        if existing_tags
-                        else list(cast(list, v))
+                        [*existing_tags, *new_tags] if existing_tags else 
new_tags
                     )
                 else:
                     empty[k] = v  # type: ignore[literal-required]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.17/langgraph/channels/delta.py 
new/langgraph-1.2.18/langgraph/channels/delta.py
--- old/langgraph-1.2.17/langgraph/channels/delta.py    2020-02-02 
01:00:00.000000000 +0100
+++ new/langgraph-1.2.18/langgraph/channels/delta.py    2020-02-02 
01:00:00.000000000 +0100
@@ -60,7 +60,7 @@
         typ: The value type (e.g. `list`, `dict`). Inferred automatically
             from the outer type when used inside `Annotated[T, 
DeltaChannel(...)]`.
         snapshot_frequency: Every Nth update to this channel writes a snapshot
-            blob (default `1000`). Must be a positive int.
+            blob (default `100`). Must be a positive int.
     """
 
     __slots__ = ("value", "reducer", "snapshot_frequency")
@@ -71,7 +71,7 @@
         reducer: Callable[[Any, Sequence[Any]], Any],
         typ: type[Value] | None = None,
         *,
-        snapshot_frequency: int = 1000,
+        snapshot_frequency: int = 100,
     ) -> None:
         if snapshot_frequency <= 0:
             raise ValueError(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.17/langgraph/pregel/_executor.py 
new/langgraph-1.2.18/langgraph/pregel/_executor.py
--- old/langgraph-1.2.17/langgraph/pregel/_executor.py  2020-02-02 
01:00:00.000000000 +0100
+++ new/langgraph-1.2.18/langgraph/pregel/_executor.py  2020-02-02 
01:00:00.000000000 +0100
@@ -3,6 +3,7 @@
 import asyncio
 import concurrent.futures
 import time
+import weakref
 from collections.abc import Awaitable, Callable, Coroutine
 from contextlib import AbstractAsyncContextManager, AbstractContextManager, 
ExitStack
 from contextvars import copy_context
@@ -37,6 +38,13 @@
     ) -> concurrent.futures.Future[T]: ...
 
 
+# Futures whose exception a node's error handler took over; the executors
+# don't re-raise them on exit.
+SKIP_RERAISE_SET: weakref.WeakSet[concurrent.futures.Future | asyncio.Future] 
= (
+    weakref.WeakSet()
+)
+
+
 class BackgroundExecutor(AbstractContextManager):
     """A context manager that runs sync tasks in the background.
     Uses a thread pool executor to delegate tasks to separate threads.
@@ -111,7 +119,7 @@
         if exc_type is None:
             # re-raise the first exception that occurred in a task
             for task, (_, reraise) in tasks.items():
-                if not reraise:
+                if not reraise or task in SKIP_RERAISE_SET:
                     continue
                 try:
                     task.result()
@@ -202,7 +210,7 @@
         if exc_type is None:
             # re-raise the first exception that occurred in a task
             for task, (_, reraise) in tasks.items():
-                if not reraise:
+                if not reraise or task in SKIP_RERAISE_SET:
                     continue
                 try:
                     if exc := task.exception():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.17/langgraph/pregel/_loop.py 
new/langgraph-1.2.18/langgraph/pregel/_loop.py
--- old/langgraph-1.2.17/langgraph/pregel/_loop.py      2020-02-02 
01:00:00.000000000 +0100
+++ new/langgraph-1.2.18/langgraph/pregel/_loop.py      2020-02-02 
01:00:00.000000000 +0100
@@ -657,48 +657,11 @@
         # check if iteration limit is reached
         if self.step > self.stop:
             self.status = "out_of_steps"
+            # stream the checkpoint the last step saved, as a tick that runs 
would
+            self._prepare_tasks_and_emit_checkpoint()
             return False
 
-        # prepare next tasks
-        self.tasks = prepare_next_tasks(
-            self.checkpoint,
-            self.checkpoint_pending_writes,
-            self.nodes,
-            self.channels,
-            self.managed,
-            self.config,
-            self.step,
-            self.stop,
-            for_execution=True,
-            manager=self.manager,
-            store=self.store,
-            checkpointer=self.checkpointer,
-            trigger_to_nodes=self.trigger_to_nodes,
-            updated_channels=self.updated_channels,
-            retry_policy=self.retry_policy,
-            cache_policy=self.cache_policy,
-        )
-
-        # produce debug output
-        if self._checkpointer_put_after_previous is not None:
-            self._emit(
-                "checkpoints",
-                map_debug_checkpoint,
-                {
-                    **self.checkpoint_config,
-                    CONF: {
-                        **self.checkpoint_config[CONF],
-                        CONFIG_KEY_CHECKPOINT_ID: self.checkpoint["id"],
-                    },
-                },
-                self.channels,
-                self.stream_keys,
-                self.checkpoint_metadata,
-                self.tasks.values(),
-                self.checkpoint_pending_writes,
-                self.prev_checkpoint_config,
-                self.output_keys,
-            )
+        self._prepare_tasks_and_emit_checkpoint()
 
         # if no more tasks, we're done
         if not self.tasks:
@@ -806,10 +769,53 @@
             self.checkpoint, self.interrupt_after, self.tasks.values()
         ):
             self.status = "interrupt_after"
+            # the next tick, which streams this checkpoint, never runs
+            self._prepare_tasks_and_emit_checkpoint()
             raise GraphInterrupt()
         # unset resuming flag
         self.config[CONF].pop(CONFIG_KEY_RESUMING, None)
 
+    def _prepare_tasks_and_emit_checkpoint(self) -> None:
+        self.tasks = prepare_next_tasks(
+            self.checkpoint,
+            self.checkpoint_pending_writes,
+            self.nodes,
+            self.channels,
+            self.managed,
+            self.config,
+            self.step,
+            self.stop,
+            for_execution=True,
+            manager=self.manager,
+            store=self.store,
+            checkpointer=self.checkpointer,
+            trigger_to_nodes=self.trigger_to_nodes,
+            updated_channels=self.updated_channels,
+            retry_policy=self.retry_policy,
+            cache_policy=self.cache_policy,
+        )
+
+        # produce debug output
+        if self._checkpointer_put_after_previous is not None:
+            self._emit(
+                "checkpoints",
+                map_debug_checkpoint,
+                {
+                    **self.checkpoint_config,
+                    CONF: {
+                        **self.checkpoint_config[CONF],
+                        CONFIG_KEY_CHECKPOINT_ID: self.checkpoint["id"],
+                    },
+                },
+                self.channels,
+                self.stream_keys,
+                self.checkpoint_metadata,
+                self.tasks.values(),
+                self.checkpoint_pending_writes,
+                self.prev_checkpoint_config,
+                self.output_keys,
+            )
+
     def match_cached_writes(self) -> Sequence[PregelExecutableTask]:
         raise NotImplementedError
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.17/langgraph/pregel/_runner.py 
new/langgraph-1.2.18/langgraph/pregel/_runner.py
--- old/langgraph-1.2.17/langgraph/pregel/_runner.py    2020-02-02 
01:00:00.000000000 +0100
+++ new/langgraph-1.2.18/langgraph/pregel/_runner.py    2020-02-02 
01:00:00.000000000 +0100
@@ -43,7 +43,7 @@
 from langgraph.constants import TAG_HIDDEN
 from langgraph.errors import GraphBubbleUp, GraphInterrupt, is_invalid_resume
 from langgraph.pregel._algo import Call
-from langgraph.pregel._executor import Submit
+from langgraph.pregel._executor import SKIP_RERAISE_SET, Submit
 from langgraph.pregel._retry import arun_with_retry, run_with_retry
 from langgraph.pregel._task_status import CONTROL_WRITES
 from langgraph.types import (
@@ -68,10 +68,6 @@
     "concurrent/futures/_base.py",
 )
 
-SKIP_RERAISE_SET: weakref.WeakSet[concurrent.futures.Future | asyncio.Future] 
= (
-    weakref.WeakSet()
-)
-
 
 class FuturesDict(Generic[F, E], dict[F, PregelExecutableTask | None]):
     event: E
@@ -239,6 +235,8 @@
                         fut.set_exception(exc)
                         futures.done.add(fut)
                 elif reraise and id(exc) not in self._handled_exception_ids:
+                    # let the caller stream this task's result before it raises
+                    yield
                     if tb := exc.__traceback__:
                         while tb.tb_next is not None and any(
                             tb.tb_frame.f_code.co_filename.endswith(name)
@@ -430,6 +428,8 @@
                         fut.set_exception(exc)
                         futures.done.add(fut)
                 elif reraise and id(exc) not in self._handled_exception_ids:
+                    # let the caller stream this task's result before it raises
+                    yield
                     if tb := exc.__traceback__:
                         while tb.tb_next is not None and any(
                             tb.tb_frame.f_code.co_filename.endswith(name)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.17/pyproject.toml 
new/langgraph-1.2.18/pyproject.toml
--- old/langgraph-1.2.17/pyproject.toml 2020-02-02 01:00:00.000000000 +0100
+++ new/langgraph-1.2.18/pyproject.toml 2020-02-02 01:00:00.000000000 +0100
@@ -4,7 +4,7 @@
 
 [project]
 name = "langgraph"
-version = "1.2.17"
+version = "1.2.18"
 description = "Building stateful, multi-actor applications with LLMs"
 authors = []
 requires-python = ">=3.10"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.17/tests/test_channels.py 
new/langgraph-1.2.18/tests/test_channels.py
--- old/langgraph-1.2.17/tests/test_channels.py 2020-02-02 01:00:00.000000000 
+0100
+++ new/langgraph-1.2.18/tests/test_channels.py 2020-02-02 01:00:00.000000000 
+0100
@@ -20,6 +20,7 @@
 from langgraph.graph import START, StateGraph
 from langgraph.graph.message import _messages_delta_reducer
 from langgraph.graph.state import _get_channel
+from langgraph.pregel._checkpoint import delta_channels_to_snapshot
 from langgraph.types import Overwrite
 
 pytestmark = pytest.mark.anyio
@@ -342,6 +343,26 @@
 # ---------------------------------------------------------------------------
 
 
[email protected]("frequency", [None, 50, 1000])
+def test_delta_channel_snapshot_frequency(frequency: int | None) -> None:
+    spec = (
+        DeltaChannel(_messages_delta_reducer)
+        if frequency is None
+        else DeltaChannel(_messages_delta_reducer, 
snapshot_frequency=frequency)
+    )
+    expected = 100 if frequency is None else frequency
+    assert spec.snapshot_frequency == expected
+    channel = spec.from_checkpoint(MISSING)
+    channel.update([HumanMessage(content="hi", id="h1")])
+    assert channel.copy().snapshot_frequency == expected
+    for updates in (expected - 1, expected):
+        assert delta_channels_to_snapshot(
+            {"messages": channel},
+            {"messages": (updates, updates)},
+            {"messages": 1},
+        ) == ({"messages"} if updates == expected else set())
+
+
 def test_delta_channel_snapshot_version_based() -> None:
     """Snapshots fire when a channel accumulates `snapshot_frequency` updates.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.17/tests/test_pregel.py 
new/langgraph-1.2.18/tests/test_pregel.py
--- old/langgraph-1.2.17/tests/test_pregel.py   2020-02-02 01:00:00.000000000 
+0100
+++ new/langgraph-1.2.18/tests/test_pregel.py   2020-02-02 01:00:00.000000000 
+0100
@@ -4346,6 +4346,25 @@
     assert graph.invoke({"foo": "hello"}) == {"foo": "hello", "bar": "hello!"}
 
 
[email protected]("stream_mode", ["tasks", "debug"])
+def test_failing_task_result_is_streamed_before_the_error(stream_mode: str) -> 
None:
+    class State(TypedDict):
+        x: str
+
+    def failing(state: State) -> State:
+        raise ValueError("boom")
+
+    graph = StateGraph(State).add_node("failing", failing).add_edge(START, 
"failing")
+    errors = []
+    with pytest.raises(ValueError, match="boom"):
+        for chunk in graph.compile().stream({"x": ""}, 
stream_mode=stream_mode):
+            payload = chunk["payload"] if stream_mode == "debug" else chunk
+            if "error" in payload:
+                errors.append(payload["error"])
+
+    assert [type(error) for error in errors] == [ValueError]
+
+
 def test_debug_retry(sync_checkpointer: BaseCheckpointSaver):
     class State(TypedDict):
         messages: Annotated[list[str], operator.add]
@@ -4410,6 +4429,52 @@
         assert stream_parent_conf == history_parent_conf
 
 
+def test_interrupt_after_streams_the_checkpoint_it_pauses_on(
+    sync_checkpointer: BaseCheckpointSaver,
+) -> None:
+    class State(TypedDict):
+        x: int
+
+    graph = (
+        StateGraph(State)
+        .add_node("one", lambda state: {"x": 1})
+        .add_node("two", lambda state: {"x": 2})
+        .add_edge(START, "one")
+        .add_edge("one", "two")
+        .compile(checkpointer=sync_checkpointer, interrupt_after=["one"])
+    )
+    config = {"configurable": {"thread_id": "1"}}
+
+    checkpoints = list(graph.stream({"x": 0}, config, 
stream_mode="checkpoints"))
+
+    state = graph.get_state(config)
+    assert checkpoints[-1]["config"] == state.config
+    assert checkpoints[-1]["next"] == ["two"]
+
+
+def test_recursion_limit_streams_the_last_checkpoint_saved(
+    sync_checkpointer: BaseCheckpointSaver,
+) -> None:
+    class State(TypedDict):
+        x: int
+
+    graph = (
+        StateGraph(State)
+        .add_node("loop", lambda state: {"x": state["x"] + 1})
+        .add_edge(START, "loop")
+        .add_edge("loop", "loop")
+        .compile(checkpointer=sync_checkpointer)
+    )
+    config = {"configurable": {"thread_id": "1"}, "recursion_limit": 3}
+    checkpoints = []
+
+    with pytest.raises(GraphRecursionError):
+        for chunk in graph.stream({"x": 0}, config, stream_mode="checkpoints"):
+            checkpoints.append(chunk)
+
+    assert checkpoints[-1]["config"] == graph.get_state(config).config
+
+
 def test_debug_subgraphs(
     sync_checkpointer: BaseCheckpointSaver, durability: Durability
 ):
@@ -6880,6 +6945,22 @@
     ]
 
 
+def test_invoke_tags_reach_a_node_three_subgraphs_deep_once() -> None:
+    class State(TypedDict):
+        copies: int
+
+    def leaf(state: State, config: RunnableConfig) -> State:
+        return {"copies": config["tags"].count("my-tag")}
+
+    graph = StateGraph(State).add_node("leaf", leaf).add_edge(START, 
"leaf").compile()
+    for _ in range(3):
+        graph = (
+            StateGraph(State).add_node("sub", graph).add_edge(START, 
"sub").compile()
+        )
+
+    assert graph.invoke({"copies": 0}, {"tags": ["my-tag"]}) == {"copies": 1}
+
+
 def test_tags_stream_mode_messages() -> None:
     model = GenericFakeChatModel(messages=iter(["foo"]), tags=["meow"])
     graph = (
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.17/tests/test_pregel_async.py 
new/langgraph-1.2.18/tests/test_pregel_async.py
--- old/langgraph-1.2.17/tests/test_pregel_async.py     2020-02-02 
01:00:00.000000000 +0100
+++ new/langgraph-1.2.18/tests/test_pregel_async.py     2020-02-02 
01:00:00.000000000 +0100
@@ -5809,6 +5809,60 @@
     )  # still overwriting the same one
 
 
[email protected]("stream_mode", ["tasks", "debug"])
+async def test_failing_task_result_is_streamed_before_the_error(
+    stream_mode: str,
+) -> None:
+    class State(TypedDict):
+        x: str
+
+    async def failing(state: State) -> State:
+        raise ValueError("boom")
+
+    graph = StateGraph(State).add_node("failing", failing).add_edge(START, 
"failing")
+    errors = []
+    with pytest.raises(ValueError, match="boom"):
+        async for chunk in graph.compile().astream({"x": ""}, 
stream_mode=stream_mode):
+            payload = chunk["payload"] if stream_mode == "debug" else chunk
+            if "error" in payload:
+                errors.append(payload["error"])
+
+    assert [type(error) for error in errors] == [ValueError]
+
+
[email protected](
+    ("parallel", "stream_mode"), [(False, "custom"), (True, "values")]
+)
+async def test_graph_error_handler_recovers_on_the_executor_path(
+    parallel: bool, stream_mode: str
+) -> None:
+    class State(TypedDict):
+        x: str
+
+    async def failing(state: State) -> State:
+        raise ValueError("boom")
+
+    async def handler(state: State) -> Command:
+        return Command(update={"x": "handled"}, goto=END)
+
+    builder = (
+        StateGraph(State)
+        .add_node("failing", failing, error_handler=handler)
+        .add_edge(START, "failing")
+    )
+    if parallel:
+        builder.add_node("sibling", lambda state: {}).add_edge(START, 
"sibling")
+
+    chunks = [
+        chunk
+        async for chunk in builder.compile().astream(
+            {"x": ""}, stream_mode=[stream_mode, "values"]
+        )
+    ]
+
+    assert [c[-1] for c in chunks if c[0] == "values"][-1] == {"x": "handled"}
+
+
 async def test_debug_retry(async_checkpointer: BaseCheckpointSaver):
     class State(TypedDict):
         messages: Annotated[list[str], operator.add]
@@ -5875,6 +5929,55 @@
         assert stream_parent_conf == history_parent_conf
 
 
+async def test_interrupt_after_streams_the_checkpoint_it_pauses_on(
+    async_checkpointer: BaseCheckpointSaver,
+) -> None:
+    class State(TypedDict):
+        x: int
+
+    graph = (
+        StateGraph(State)
+        .add_node("one", lambda state: {"x": 1})
+        .add_node("two", lambda state: {"x": 2})
+        .add_edge(START, "one")
+        .add_edge("one", "two")
+        .compile(checkpointer=async_checkpointer, interrupt_after=["one"])
+    )
+    config = {"configurable": {"thread_id": "1"}}
+
+    checkpoints = [
+        chunk
+        async for chunk in graph.astream({"x": 0}, config, 
stream_mode="checkpoints")
+    ]
+
+    state = await graph.aget_state(config)
+    assert checkpoints[-1]["config"] == state.config
+    assert checkpoints[-1]["next"] == ["two"]
+
+
+async def test_recursion_limit_streams_the_last_checkpoint_saved(
+    async_checkpointer: BaseCheckpointSaver,
+) -> None:
+    class State(TypedDict):
+        x: int
+
+    graph = (
+        StateGraph(State)
+        .add_node("loop", lambda state: {"x": state["x"] + 1})
+        .add_edge(START, "loop")
+        .add_edge("loop", "loop")
+        .compile(checkpointer=async_checkpointer)
+    )
+    config = {"configurable": {"thread_id": "1"}, "recursion_limit": 3}
+    checkpoints = []
+
+    with pytest.raises(GraphRecursionError):
+        async for chunk in graph.astream({"x": 0}, config, 
stream_mode="checkpoints"):
+            checkpoints.append(chunk)
+
+    assert checkpoints[-1]["config"] == (await graph.aget_state(config)).config
+
+
 async def test_debug_subgraphs(
     async_checkpointer: BaseCheckpointSaver, durability: Durability
 ):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.17/tests/test_retry.py 
new/langgraph-1.2.18/tests/test_retry.py
--- old/langgraph-1.2.17/tests/test_retry.py    2020-02-02 01:00:00.000000000 
+0100
+++ new/langgraph-1.2.18/tests/test_retry.py    2020-02-02 01:00:00.000000000 
+0100
@@ -2097,6 +2097,47 @@
     assert result["foo"] == "handled_next"
 
 
+# Each case runs the failing node on the executor path instead of the
+# single-task fast path.
[email protected](
+    ("parallel", "stream_mode", "subgraphs"),
+    [
+        (False, "custom", False),
+        (False, "messages", False),
+        (False, "values", True),
+        (True, "values", False),
+    ],
+)
+def test_graph_error_handler_recovers_on_the_executor_path(
+    parallel: bool, stream_mode: str, subgraphs: bool
+) -> None:
+    class State(TypedDict):
+        x: str
+
+    def failing(state: State) -> State:
+        raise ValueError("boom")
+
+    def handler(state: State) -> Command:
+        return Command(update={"x": "handled"}, goto=END)
+
+    builder = (
+        StateGraph(State)
+        .add_node("failing", failing, error_handler=handler)
+        .add_edge(START, "failing")
+    )
+    if parallel:
+        builder.add_node("sibling", lambda state: {}).add_edge(START, 
"sibling")
+    graph = builder.compile()
+
+    chunks = list(
+        graph.stream(
+            {"x": ""}, stream_mode=[stream_mode, "values"], subgraphs=subgraphs
+        )
+    )
+
+    assert [c[-1] for c in chunks if c[-2] == "values"][-1] == {"x": "handled"}
+
+
 def test_graph_error_handler_failure_fails_run():
     class State(TypedDict):
         foo: str
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.17/tests/test_utils.py 
new/langgraph-1.2.18/tests/test_utils.py
--- old/langgraph-1.2.17/tests/test_utils.py    2020-02-02 01:00:00.000000000 
+0100
+++ new/langgraph-1.2.18/tests/test_utils.py    2020-02-02 01:00:00.000000000 
+0100
@@ -799,6 +799,15 @@
     assert merged["tags"] == ["shared", "alpha", "shared", "beta"]
 
 
+def test_ensure_config_skips_tags_inherited_from_ambient() -> None:
+    token = var_child_runnable_config.set({"tags": ["inherited"]})
+    try:
+        merged = ensure_config({"tags": ["inherited", "own"]})
+    finally:
+        var_child_runnable_config.reset(token)
+    assert merged["tags"] == ["inherited", "own"]
+
+
 def test_ensure_config_merges_callbacks_across_configs() -> None:
     a_cb = _TrackingCB("a")
     b_cb = _TrackingCB("b")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.17/uv.lock new/langgraph-1.2.18/uv.lock
--- old/langgraph-1.2.17/uv.lock        2020-02-02 01:00:00.000000000 +0100
+++ new/langgraph-1.2.18/uv.lock        2020-02-02 01:00:00.000000000 +0100
@@ -1,5 +1,5 @@
 version = 1
-revision = 3
+revision = 5
 requires-python = ">=3.10"
 resolution-markers = [
     "python_full_version >= '3.15'",
@@ -932,18 +932,6 @@
 ]
 
 [[package]]
-name = "importlib-metadata"
-version = "8.7.1"
-source = { registry = "https://pypi.org/simple"; }
-dependencies = [
-    { name = "zipp" },
-]
-sdist = { url = 
"https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz";,
 hash = 
"sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size 
= 57107, upload-time = "2025-12-21T10:00:19.278Z" }
-wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl";,
 hash = 
"sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size 
= 27865, upload-time = "2025-12-21T10:00:18.329Z" },
-]
-
-[[package]]
 name = "iniconfig"
 version = "2.3.0"
 source = { registry = "https://pypi.org/simple"; }
@@ -1451,7 +1439,7 @@
 
 [[package]]
 name = "langgraph"
-version = "1.2.17"
+version = "1.2.18"
 source = { editable = "." }
 dependencies = [
     { name = "langchain-core" },
@@ -1637,7 +1625,7 @@
 
 [[package]]
 name = "langgraph-checkpoint"
-version = "4.3.0"
+version = "4.3.1"
 source = { editable = "../checkpoint" }
 dependencies = [
     { name = "langchain-core" },
@@ -2111,7 +2099,7 @@
 
 [[package]]
 name = "nbconvert"
-version = "7.17.1"
+version = "7.17.2"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
     { name = "beautifulsoup4" },
@@ -2129,9 +2117,9 @@
     { name = "pygments" },
     { name = "traitlets" },
 ]
-sdist = { url = 
"https://files.pythonhosted.org/packages/01/b1/708e53fe2e429c103c6e6e159106bcf0357ac41aa4c28772bd8402339051/nbconvert-7.17.1.tar.gz";,
 hash = 
"sha256:34d0d0a7e73ce3cbab6c5aae8f4f468797280b01fd8bd2ca746da8569eddd7d2", size 
= 865311, upload-time = "2026-04-08T00:44:14.914Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/53/6a/252c7620305a9661205aed7a2a4b3592f5207bfca56ad11b7dea2c5ec2f6/nbconvert-7.17.2.tar.gz";,
 hash = 
"sha256:344a218e8595117ce37396ed025d994f146d076226ebac59255a2fa085ab24d8", size 
= 865989, upload-time = "2026-10-12T09:11:19.583Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/67/f8/bb0a9d5f46819c821dc1f004aa2cc29b1d91453297dbf5ff20470f00f193/nbconvert-7.17.1-py3-none-any.whl";,
 hash = 
"sha256:aa85c087b435e7bf1ffd03319f658e285f2b89eccab33bc1ba7025495ab3e7c8", size 
= 261927, upload-time = "2026-04-08T00:44:12.845Z" },
+    { url = 
"https://files.pythonhosted.org/packages/81/fd/a8d457af68848a8e5c12dd1d84ff35c415db471af78280e0703db4efbb12/nbconvert-7.17.2-py3-none-any.whl";,
 hash = 
"sha256:6a143c11c00bf831604da4b1be4347d36ea52367057eb920135d69bc2862fed2", size 
= 262094, upload-time = "2026-10-12T09:11:17.215Z" },
 ]
 
 [[package]]
@@ -2189,84 +2177,114 @@
 
 [[package]]
 name = "opentelemetry-api"
-version = "1.39.1"
+version = "1.45.1"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
-    { name = "importlib-metadata" },
     { name = "typing-extensions" },
 ]
-sdist = { url = 
"https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz";,
 hash = 
"sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size 
= 65767, upload-time = "2025-12-11T13:32:39.182Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz";,
 hash = 
"sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size 
= 72804, upload-time = "2026-10-06T17:32:58.133Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl";,
 hash = 
"sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size 
= 66356, upload-time = "2025-12-11T13:32:17.304Z" },
+    { url = 
"https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl";,
 hash = 
"sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size 
= 60256, upload-time = "2026-10-06T17:32:33.506Z" },
+]
+
+[[package]]
+name = "opentelemetry-exporter-http-transport"
+version = "0.66b1"
+source = { registry = "https://pypi.org/simple"; }
+dependencies = [
+    { name = "opentelemetry-api" },
+]
+sdist = { url = 
"https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz";,
 hash = 
"sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size 
= 11693, upload-time = "2026-10-06T17:32:59.65Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl";,
 hash = 
"sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size 
= 12155, upload-time = "2026-10-06T17:32:35.454Z" },
+]
+
+[package.optional-dependencies]
+requests = [
+    { name = "requests" },
+]
+
+[[package]]
+name = "opentelemetry-exporter-otlp-common"
+version = "0.66b1"
+source = { registry = "https://pypi.org/simple"; }
+dependencies = [
+    { name = "opentelemetry-sdk" },
+]
+sdist = { url = 
"https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz";,
 hash = 
"sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size 
= 14325, upload-time = "2026-10-06T17:33:01.725Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl";,
 hash = 
"sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size 
= 12385, upload-time = "2026-10-06T17:32:38.177Z" },
 ]
 
 [[package]]
 name = "opentelemetry-exporter-otlp-proto-common"
-version = "1.39.1"
+version = "1.45.1"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
     { name = "opentelemetry-proto" },
 ]
-sdist = { url = 
"https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz";,
 hash = 
"sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size 
= 20409, upload-time = "2025-12-11T13:32:40.885Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz";,
 hash = 
"sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size 
= 18873, upload-time = "2026-10-06T17:33:04.471Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/8c/02/ffc3e143d89a27ac21fd557365b98bd0653b98de8a101151d5805b5d4c33/opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl";,
 hash = 
"sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde", size 
= 18366, upload-time = "2025-12-11T13:32:20.2Z" },
+    { url = 
"https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl";,
 hash = 
"sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size 
= 15393, upload-time = "2026-10-06T17:32:41.911Z" },
 ]
 
 [[package]]
 name = "opentelemetry-exporter-otlp-proto-http"
-version = "1.39.1"
+version = "1.45.1"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
     { name = "googleapis-common-protos" },
     { name = "opentelemetry-api" },
+    { name = "opentelemetry-exporter-http-transport", extra = ["requests"] },
+    { name = "opentelemetry-exporter-otlp-common" },
     { name = "opentelemetry-exporter-otlp-proto-common" },
     { name = "opentelemetry-proto" },
     { name = "opentelemetry-sdk" },
     { name = "requests" },
     { name = "typing-extensions" },
 ]
-sdist = { url = 
"https://files.pythonhosted.org/packages/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz";,
 hash = 
"sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb", size 
= 17288, upload-time = "2025-12-11T13:32:42.029Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz";,
 hash = 
"sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size 
= 28839, upload-time = "2026-10-06T17:33:05.713Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/95/f1/b27d3e2e003cd9a3592c43d099d2ed8d0a947c15281bf8463a256db0b46c/opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl";,
 hash = 
"sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985", size 
= 19641, upload-time = "2025-12-11T13:32:22.248Z" },
+    { url = 
"https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl";,
 hash = 
"sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size 
= 22180, upload-time = "2026-10-06T17:32:43.946Z" },
 ]
 
 [[package]]
 name = "opentelemetry-proto"
-version = "1.39.1"
+version = "1.45.1"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
     { name = "protobuf" },
 ]
-sdist = { url = 
"https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz";,
 hash = 
"sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size 
= 46152, upload-time = "2025-12-11T13:32:48.681Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz";,
 hash = 
"sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size 
= 46488, upload-time = "2026-10-06T17:33:11.49Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl";,
 hash = 
"sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size 
= 72535, upload-time = "2025-12-11T13:32:33.866Z" },
+    { url = 
"https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl";,
 hash = 
"sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size 
= 72488, upload-time = "2026-10-06T17:32:53.057Z" },
 ]
 
 [[package]]
 name = "opentelemetry-sdk"
-version = "1.39.1"
+version = "1.45.1"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
     { name = "opentelemetry-api" },
     { name = "opentelemetry-semantic-conventions" },
     { name = "typing-extensions" },
 ]
-sdist = { url = 
"https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz";,
 hash = 
"sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size 
= 171460, upload-time = "2025-12-11T13:32:49.369Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz";,
 hash = 
"sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size 
= 218324, upload-time = "2026-10-06T17:33:13.26Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl";,
 hash = 
"sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size 
= 132565, upload-time = "2025-12-11T13:32:35.069Z" },
+    { url = 
"https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl";,
 hash = 
"sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size 
= 140063, upload-time = "2026-10-06T17:32:55.04Z" },
 ]
 
 [[package]]
 name = "opentelemetry-semantic-conventions"
-version = "0.60b1"
+version = "0.66b1"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
     { name = "opentelemetry-api" },
     { name = "typing-extensions" },
 ]
-sdist = { url = 
"https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz";,
 hash = 
"sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size 
= 137935, upload-time = "2025-12-11T13:32:50.487Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz";,
 hash = 
"sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size 
= 150250, upload-time = "2026-10-06T17:33:14.073Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl";,
 hash = 
"sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size 
= 219982, upload-time = "2025-12-11T13:32:36.955Z" },
+    { url = 
"https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl";,
 hash = 
"sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size 
= 206279, upload-time = "2026-10-06T17:32:56.103Z" },
 ]
 
 [[package]]
@@ -4240,15 +4258,6 @@
 ]
 
 [[package]]
-name = "zipp"
-version = "3.23.0"
-source = { registry = "https://pypi.org/simple"; }
-sdist = { url = 
"https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz";,
 hash = 
"sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size 
= 25547, upload-time = "2025-06-08T17:06:39.4Z" }
-wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl";,
 hash = 
"sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size 
= 10276, upload-time = "2025-06-08T17:06:38.034Z" },
-]
-
-[[package]]
 name = "zstandard"
 version = "0.25.0"
 source = { registry = "https://pypi.org/simple"; }

Reply via email to