{"record":{"id":"b779e34c8ef66431","repo":"langchain-ai/langchain","slug":"runnable-step-has-no-first-node","errorCode":null,"errorMessage":"Runnable {step} has no first node","messagePattern":"Runnable (.+?) has no first node","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/runnables/base.py","lineNumber":3307,"sourceCode":"        Raises:\n            ValueError: If a `Runnable` has no first or last node.\n\n        \"\"\"\n        # Import locally to prevent circular import\n        from langchain_core.runnables.graph import Graph  # noqa: PLC0415\n\n        graph = Graph()\n        for step in self.steps:\n            current_last_node = graph.last_node()\n            step_graph = step.get_graph(config)\n            if step is not self.first:\n                step_graph.trim_first_node()\n            if step is not self.last:\n                step_graph.trim_last_node()\n            step_first_node, _ = graph.extend(step_graph)\n            if not step_first_node:\n                msg = f\"Runnable {step} has no first node\"\n                raise ValueError(msg)\n            if current_last_node:\n                graph.add_edge(current_last_node, step_first_node)\n\n        return graph\n\n    @override\n    def __repr__(self) -> str:\n        return \"\\n| \".join(\n            repr(s) if i == 0 else indent_lines_after_first(repr(s), \"| \")\n            for i, s in enumerate(self.steps)\n        )\n\n    @overload\n    def __or__(\n        self, other: Mapping[str, Any]\n    ) -> RunnableSerializable[Input, dict[str, Any]]: ...\n\n    @overload","sourceCodeStart":3289,"sourceCodeEnd":3325,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/runnables/base.py#L3289-L3325","documentation":"While building the compute graph for a `RunnableSequence`, each step's own graph has its boundary nodes trimmed (first node trimmed for non-first steps, last node trimmed for non-last steps) and is then spliced in with `graph.extend`. If a step's trimmed graph yields no first node, the sequence cannot be stitched together and a `ValueError` is raised. This almost always means a custom `Runnable` subclass returns an empty or malformed graph from `get_graph()`.","triggerScenarios":"Embedding a custom `Runnable` whose `get_graph()` returns a `Graph` with no nodes (or only a last node) inside a `RunnableSequence`, then calling `sequence.get_graph()`, `.get_repr()`, or rendering with `LangChain`/`langgraph` visualization tools that call `get_graph().draw_ascii()`/`.print_ascii()`.","commonSituations":"Writing a lightweight custom `Runnable` and not overriding (or incorrectly overriding) `get_graph()`; a custom runnable whose graph gets fully consumed by `trim_first_node()` because it has a single node; visualizing pipelines containing third-party runnables with broken graph support.","solutions":["Override `get_graph()` in your custom `Runnable` to return a graph with at least a real node: `graph = Graph(); graph.add_node('my_runnable', self); return graph`.","Check `len(my_runnable.get_graph().nodes) > 0` before adding it to a sequence.","Wrap the offending runnable in a `RunnableLambda` (`RunnableLambda(fn)`), whose graph implementation is well-formed.","If the runnable is trivially single-node, ensure `trim_first_node()`/`trim_last_node()` leave at least the boundary node the sequence needs."],"exampleFix":"// before\nclass MyRunnable(Runnable):\n    def invoke(self, x, config=None):\n        return x\n    # get_graph not overridden -> may extend to no first node\n\n// after\nclass MyRunnable(Runnable):\n    def invoke(self, x, config=None):\n        return x\n    def get_graph(self, config=None):\n        graph = Graph()\n        graph.add_node('MyRunnable', self)\n        return graph","handlingStrategy":"validation","validationCode":"from langchain_core.runnables import Runnable\n\ndef graph_is_stitchable(step: Runnable) -> bool:\n    g = step.get_graph()\n    g.trim_first_node()\n    g.trim_last_node()\n    return bool(g.nodes)","typeGuard":null,"tryCatchPattern":"try:\n    graph = sequence.get_graph()\nexcept ValueError as e:\n    if 'has no first node' in str(e):\n        # identify and fix/replace the offending step\n        raise\n    raise","preventionTips":["Override get_graph() in custom Runnables to add at least one node.","Wrap unknown runnables in RunnableLambda before composing sequences.","Test get_graph() on every custom runnable as part of its unit tests."],"tags":["runnable","runnable-sequence","graph","get-graph","custom-runnable"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}