-
Notifications
You must be signed in to change notification settings - Fork 20.1k
Description
Checked other resources
- This is a bug, not a usage question.
- I added a clear and descriptive title that summarizes this issue.
- I used the GitHub search to find a similar question and didn't find it.
- I am sure that this is a bug in LangChain rather than my code.
- The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).
- This is not related to the langchain-community package.
- I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.
Package (Required)
- langchain
- langchain-openai
- langchain-anthropic
- langchain-classic
- langchain-core
- langchain-cli
- langchain-model-profiles
- langchain-tests
- langchain-text-splitters
- langchain-chroma
- langchain-deepseek
- langchain-exa
- langchain-fireworks
- langchain-groq
- langchain-huggingface
- langchain-mistralai
- langchain-nomic
- langchain-ollama
- langchain-perplexity
- langchain-prompty
- langchain-qdrant
- langchain-xai
- Other / not sure / general
Example Code (Python)
import functools
import operator
import asyncio
from typing import TypedDict, List, Callable, Annotated
from langgraph.errors import GraphInterrupt
from langgraph.graph import StateGraph, END
from langgraph.types import Command
def error_handler():
def decorator(func: Callable):
@functools.wraps(func)
async def async_wrapper(*args, **kwargs):
try:
print(f"Starting execution: {func.__name__}")
result = await func(*args, **kwargs)
print(f"Successfully completed: {func.__name__}")
return result
except GraphInterrupt:
raise
except Exception as e:
print(f"Error in {func.__name__}: {str(e)}")
# Attempt to redirect to exception node
return Command(goto="exception_node")
return async_wrapper
return decorator
class GraphState(TypedDict):
messages: Annotated[List[str], operator.add]
@error_handler()
async def node_a(state: GraphState):
print("---Executing Node A---")
raise ValueError("Test error from Node A") # This should trigger the jump
return {"messages": ["Message from Node A"]}
@error_handler()
async def node_b(state: GraphState):
print("---Executing Node B---")
return {"messages": ["Message from Node B"]}
async def exception_node(state: GraphState):
print("---Executing Exception_node---")
return {"messages": ["An error occurred"]}
# Build graph
workflow = StateGraph(GraphState)
workflow.add_node("A", node_a)
workflow.add_node("B", node_b)
workflow.add_node("exception_node", exception_node)
workflow.set_entry_point("A")
workflow.add_edge("A", "B") # Explicit edge
workflow.add_edge("B", END)
workflow.add_edge("exception_node", END)
app = workflow.compile()
async def main():
inputs = {"messages": []}
print("\n---Running---")
final_state = await app.ainvoke(inputs)
print("---Final State---")
print(final_state)
if __name__ == "__main__":
asyncio.run(main())Error Message and Stack Trace (if applicable)
Actual Output:
---Running---
Starting execution: node_a
---Executing Node A---
Error in node_a: Test error from Node A
Starting execution: node_b # UNEXPECTED: Node B should not run
---Executing Node B---
Successfully completed: node_b
---Executing Exception_node---
---Final State---
{'messages': ['Message from Node B', 'An error occurred']} # State merged from both paths
Expected Output:
---Running---
Starting execution: node_a
---Executing Node A---
Error in node_a: Test error from Node A
---Executing Exception_node--- # ONLY the exception node should run
---Final State---
{'messages': ['An error occurred']}Description
I implemented a unified error-handling decorator for all nodes in a StateGraph. The decorator catches exceptions and attempts to redirect the flow to a dedicated exception_node by returning a Command(goto="exception_node").
However, when a node throws an exception, the graph does not interrupt its pre-defined execution flow set by add_edge. Instead, it seems to execute both paths: the error handler triggers, but then the graph also proceeds to the next node specified by the original edge. This results in the exception_node and the subsequent normal node both being executed, which is contrary to the expected behavior where the graph should immediately jump to the exception handler and stop the normal flow.
Expected Behavior:
When a node’s error-handling decorator catches an exception and returns a Command(goto="exception_node"), I expect the graph to interrupt the current explicit edge flow and immediately transition to the specified exception_node. The subsequent node (as defined by add_edge) should not execute.
Actual Behavior:
The graph appears to not respect the interrupt from the decorator’s Command when explicit edges are defined. Both the exception_node (from the goto command) and the next node in the original chain (from add_edge) are invoked, leading to merged and incorrect state updates.
finally, we solve this issue by all use command, but it should be a good method, we can't change all our project graph to not use addadge, we should have the uniform process enhance for the whole graph node
System Info
System Information
OS: Darwin
OS Version: Darwin Kernel Version 24.6.0: Mon Aug 11 21:16:30 PDT 2025; root:xnu-11417.140.69.701.11~1/RELEASE_ARM64_T8132
Python Version: 3.12.11 (main, Dec 4 2025, 15:32:46) [Clang 17.0.0 (clang-1700.0.13.5)]