langchain-ai/langchain · error · ValueError

Runnable {step} has no last node

Error message

Runnable {step} has no last node

What it means

Mirror of the missing-first-node check in `RunnableParallel.get_graph`: after a branch's sub-graph is extended, the code verifies it also produced a last node so an edge can be drawn to the shared output schema node. If the trimmed sub-graph has a first node but no last node, a `ValueError` is raised. This indicates a branch runnable whose graph is internally inconsistent.

Source

Thrown at libs/core/langchain_core/runnables/base.py:4112

        from langchain_core.runnables.graph import Graph  # noqa: PLC0415

        graph = Graph()
        input_node = graph.add_node(self.get_input_schema(config))
        output_node = graph.add_node(self.get_output_schema(config))
        for step in self.steps__.values():
            step_graph = step.get_graph()
            step_graph.trim_first_node()
            step_graph.trim_last_node()
            if not step_graph:
                graph.add_edge(input_node, output_node)
            else:
                step_first_node, step_last_node = graph.extend(step_graph)
                if not step_first_node:
                    msg = f"Runnable {step} has no first node"
                    raise ValueError(msg)
                if not step_last_node:
                    msg = f"Runnable {step} has no last node"
                    raise ValueError(msg)
                graph.add_edge(input_node, step_first_node)
                graph.add_edge(step_last_node, output_node)

        return graph

    @override
    def __repr__(self) -> str:
        map_for_repr = ",\n  ".join(
            f"{k}: {indent_lines_after_first(repr(v), '  ' + k + ': ')}"
            for k, v in self.steps__.items()
        )
        return "{\n  " + map_for_repr + "\n}"

    @override
    def invoke(
        self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
    ) -> dict[str, Any]:
        # setup callbacks

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Fix the custom `get_graph()` so the graph ends in a definite node (the last `add_node`'s node, or set edges so a terminal node exists).
  2. Simplest robust override: single node `graph.add_node('name', self)`.
  3. Replace the branch with `RunnableLambda` to inherit a correct graph.
  4. Test custom graphs with `assert graph.last_node() is not None` before returning.

Example fix

// before
def get_graph(self, config=None):
    g = Graph()
    n = g.add_node('start', self)
    g.add_edge(n, g.add_node('end', self))  # malformed ordering
    return g

// after
def get_graph(self, config=None):
    g = Graph()
    g.add_node('my_runnable', self)
    return g
Defensive patterns

Strategy: validation

Validate before calling

from langchain_core.runnables import Runnable

def has_terminal_node(step: Runnable) -> bool:
    g = step.get_graph()
    g.trim_first_node()
    return g.last_node() is not None or not g.nodes

Try / catch

try:
    graph = parallel.get_graph()
except ValueError as e:
    if 'has no last node' in str(e):
        # fix the branch graph to end in a definite node
        raise
    raise

Prevention

When it happens

Trigger: A `RunnableParallel` branch whose custom `get_graph()` returns a graph with nodes but no designated last node (e.g. built by adding nodes/edges manually without letting `Graph` track the last node), then calling `.get_graph()` on the parallel runnable.

Common situations: Hand-built `Graph` objects in a custom runnable that call `add_node`/`add_edge` in an order where the graph cannot infer an end node; graph construction bugs in custom runnables; visualization of complex parallel pipelines.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/12e6d67c49e29c32. Report an issue: GitHub.