Skip to content

Command(goto=...) does not override explicit edges from add_edge() #6571

@Juussticee

Description

@Juussticee

Checked other resources

  • This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/).
  • I added a clear and detailed title that summarizes the issue.
  • I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
  • I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.

Example Code

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

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)]

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingpendingawaiting review/confirmation by maintainer

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions