Checked other resources
Related Issues / PRs
No response
Reproduction Steps / Example Code (Python)
import asyncio
from typing_extensions import TypedDict
from langgraph.errors import NodeError
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command
handler_calls = 0
class State(TypedDict, total=False):
x: str
y: str
def boom(state: State) -> State:
raise RuntimeError("test")
def sibling(state: State) -> State:
return {"y": "sibling"}
def handler(state: State, error: NodeError) -> Command:
global handler_calls
handler_calls += 1
return Command(update={"x": "handled"}, goto=END)
def build(parallel: bool):
"""`parallel=True` puts a second node in the same superstep as the failing one."""
builder = (
StateGraph(State)
.add_node("boom", boom, error_handler=handler)
.add_edge(START, "boom")
)
if parallel:
builder = builder.add_node("sibling", sibling).add_edge(START, "sibling")
return builder.compile()
async def collect(graph, **kwargs):
return [event async for event in graph.astream({}, **kwargs)]
CASES = [
("solo", "invoke", lambda g: g.invoke({})),
("solo", "stream values", lambda g: list(g.stream({}, stream_mode="values"))),
("solo", "stream updates", lambda g: list(g.stream({}, stream_mode="updates"))),
("solo", "stream custom", lambda g: list(g.stream({}, stream_mode="custom"))),
("solo", "stream messages", lambda g: list(g.stream({}, stream_mode="messages"))),
("solo", "stream values, subgraphs", lambda g: list(g.stream({}, stream_mode="values", subgraphs=True))),
("solo", "ainvoke", lambda g: asyncio.run(g.ainvoke({}))),
("solo", "astream custom", lambda g: asyncio.run(collect(g, stream_mode="custom"))),
("parallel", "invoke", lambda g: g.invoke({})),
("parallel", "stream values", lambda g: list(g.stream({}, stream_mode="values"))),
("parallel", "ainvoke", lambda g: asyncio.run(g.ainvoke({}))),
]
for graph_kind, how, run in CASES:
handler_calls = 0
try:
outcome = f"ok {run(build(graph_kind == 'parallel'))}"
except Exception as exc:
outcome = f"RAISED {exc!r}"
print(f"{graph_kind:9} {how:26} handler ran {handler_calls}x {outcome}")
Error Message and Stack Trace (if applicable)
From the `parallel` / `invoke` case, with the `try`/`except` taken out so the traceback shows:
Traceback (most recent call last):
File ".../langgraph/libs/langgraph/test.py", line 64, in <module>
outcome = f"ok {run(build(graph_kind == 'parallel'))}"
~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File ".../langgraph/libs/langgraph/test.py", line 56, in <lambda>
("parallel", "invoke", lambda g: g.invoke({})),
~~~~~~~~^^^^
File ".../langgraph/libs/langgraph/langgraph/pregel/main.py", line 3913, in invoke
for chunk in self.stream(
~~~~~~~~~~~^
input,
^^^^^^
...<11 lines>...
**kwargs,
^^^^^^^^^
):
^
File "...langgraph/libs/langgraph/langgraph/pregel/main.py", line 2899, in stream
with SyncPregelLoop(
~~~~~~~~~~~~~~^
input,
^^^^^^
...<18 lines>...
has_graph_lifecycle_callbacks=bool(graph_callback_manager.handlers),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
) as loop:
^
File "...langgraph/libs/langgraph/langgraph/pregel/_loop.py", line 1719, in __exit__
return self.stack.__exit__(exc_type, exc_value, traceback)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File ".../cpython-3.14.4-macos-aarch64-none/lib/python3.14/contextlib.py", line 619, in __exit__
raise exc
File ".../cpython-3.14.4-macos-aarch64-none/lib/python3.14/contextlib.py", line 604, in __exit__
if cb(*exc_details):
~~^^^^^^^^^^^^^^
File ".../langgraph/libs/langgraph/langgraph/pregel/_executor.py", line 117, in __exit__
task.result()
~~~~~~~~~~~^^
File ".../cpython-3.14.4-macos-aarch64-none/lib/python3.14/concurrent/futures/_base.py", line 443, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File ".../cpython-3.14.4-macos-aarch64-none/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
raise self._exception
File ".../langgraph/libs/langgraph/langgraph/pregel/_executor.py", line 80, in done
task.result()
~~~~~~~~~~~^^
File ".../cpython-3.14.4-macos-aarch64-none/lib/python3.14/concurrent/futures/_base.py", line 443, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File ".../cpython-3.14.4-macos-aarch64-none/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
raise self._exception
File ".../cpython-3.14.4-macos-aarch64-none/lib/python3.14/concurrent/futures/thread.py", line 86, in run
result = ctx.run(self.task)
File ".../cpython-3.14.4-macos-aarch64-none/lib/python3.14/concurrent/futures/thread.py", line 73, in run
return fn(*args, **kwargs)
File ".../langgraph/libs/langgraph/langgraph/pregel/_retry.py", line 617, in run_with_retry
return task.proc.invoke(task.input, config)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
File ".../langgraph/libs/langgraph/langgraph/_internal/_runnable.py", line 707, in invoke
input = context.run(step.invoke, input, config, **kwargs)
File ".../langgraph/libs/langgraph/langgraph/_internal/_runnable.py", line 447, in invoke
ret = self.func(*args, **kwargs)
File ".../langgraph/libs/langgraph/test.py", line 18, in boom
raise RuntimeError("test")
RuntimeError: test
During task with name 'boom' and id '55a0601e-c332-8b49-8d5f-a53824502795'
Description
- I'm using
add_node(..., error_handler=...) to catch a node exception, apply the handler's Command update, and let the graph finish cleanly.
- I expect that to work regardless of what else is running or how I'm streaming.
- Instead the original exception still comes out of the graph in most configurations. It only stays caught for a lone failing node run via
invoke/ainvoke or stream_mode="values"/"updates". Put a second node in the same superstep and even plain invoke raises; keep the node on its own but stream with stream_mode="custom", "messages", or subgraphs=True and it raises too.
Output of the script above:
solo invoke handler ran 1x ok {'x': 'handled'}
solo stream values handler ran 1x ok [{'x': 'handled'}]
solo stream updates handler ran 1x ok [{'__error_handler__boom': {'x': 'handled'}}]
solo stream custom handler ran 1x RAISED RuntimeError('test')
solo stream messages handler ran 1x RAISED RuntimeError('test')
solo stream values, subgraphs handler ran 1x RAISED RuntimeError('test')
solo ainvoke handler ran 1x ok {'x': 'handled'}
solo astream custom handler ran 1x RAISED RuntimeError('test')
parallel invoke handler ran 1x RAISED RuntimeError('test')
parallel stream values handler ran 1x RAISED RuntimeError('test')
parallel ainvoke handler ran 1x RAISED RuntimeError('test')
The handler runs exactly once in every case, including the failing ones, but the exception is just re-raised anyway afterwards. The ok rows are there as contrast; those are the configurations where it behaves as documented.
Reproduced on a clean checkout of main at 644815f (tag 1.2.11, no local modifications).
If you agree this is a bug, I'm happy to fix it and open a PR.
System Info
System Information
OS: Darwin
OS Version: Darwin Kernel Version 24.5.0: Tue Apr 22 19:54:43 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T8132
Python Version: 3.14.4 (main, Apr 14 2026, 14:46:33) [Clang 22.1.3 ]
Package Information
langchain_core: 1.5.3
langsmith: 0.8.18
langchain_protocol: 0.0.18
langgraph_sdk: 0.4.2
Optional packages not installed
deepagents
deepagents-cli
Other Dependencies
httpx: 0.28.1
jsonpatch: 1.33
orjson: 3.11.9
packaging: 25.0
pydantic: 2.13.4
pytest: 9.1.1
pyyaml: 6.0.3
requests: 2.33.1
requests-toolbelt: 1.0.0
tenacity: 9.1.2
typing-extensions: 4.15.0
uuid-utils: 0.13.0
websockets: 16.0
xxhash: 3.8.1
zstandard: 0.25.0
Checked other resources
Related Issues / PRs
No response
Reproduction Steps / Example Code (Python)
Error Message and Stack Trace (if applicable)
Description
add_node(..., error_handler=...)to catch a node exception, apply the handler'sCommandupdate, and let the graph finish cleanly.invoke/ainvokeorstream_mode="values"/"updates". Put a second node in the same superstep and even plaininvokeraises; keep the node on its own but stream withstream_mode="custom","messages", orsubgraphs=Trueand it raises too.Output of the script above:
The handler runs exactly once in every case, including the failing ones, but the exception is just re-raised anyway afterwards. The
okrows are there as contrast; those are the configurations where it behaves as documented.Reproduced on a clean checkout of
mainat 644815f (tag1.2.11, no local modifications).If you agree this is a bug, I'm happy to fix it and open a PR.
System Info
System Information
Package Information
Optional packages not installed
Other Dependencies