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 callbacksView on GitHub (pinned to e32fa9a52e)
Solutions
- 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).
- Simplest robust override: single node `graph.add_node('name', self)`.
- Replace the branch with `RunnableLambda` to inherit a correct graph.
- 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
- Keep custom get_graph() minimal: one node via add_node.
- Ensure hand-built Graphs have a node with no outgoing edges.
- Validate graph.last_node() before returning from get_graph().
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
- Runnable {step} has no first node
- Runnable {dep} has no first node
- Runnable {dep} has no last node
- RunnableSequence must have at least {_RUNNABLE_SEQUENCE_MIN_
- Expected a generator function type for `transform`.Instead g
AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14).
Data as JSON: /api/errors/12e6d67c49e29c32.
Report an issue: GitHub.