Custom component reload silently resolves to the wrong class when a file defines more than one Component subclass
Bug Description
Langflow captures a custom component's entire containing source file as its stored code field, and rebuilds a node from that stored code by taking "the first class in the file that subclasses Component" (extract_class_name, src/lfx/src/lfx/custom/validate.py):
def extract_class_name(code: str) -> str:
"""Extract the name of the first Component subclass found in the code."""
module = ast.parse(code)
for node in module.body:
if not isinstance(node, ast.ClassDef):
continue
for base in node.bases:
if isinstance(base, ast.Name) and any(pattern in base.id for pattern in ["Component", "LC"]):
return node.name
...
If a custom-components directory file defines more than one Component subclass (a natural way to organize a related bundle of components — several small components sharing helpers, in one file), every node built from that file's stored code — regardless of which class it's actually supposed to be — silently resolves to the same class: whichever one is textually first. There's no validation anywhere in the pipeline that catches this; it only surfaces once a node is rebuilt from its stored template (e.g. loading a saved flow), not when the class is freshly imported directly.
The failure mode is actively misleading. Building a node meant to be IngestPrep while the file's first class is ClaimClip produces:
Error building Component Ingest Prep: Attribute video_data_out not found in ClaimClip
— an attribute error that gives no indication the real cause is "wrong class was instantiated," making this very hard to diagnose without already suspecting the loader mechanism.
Reproduction
- In a
custom_componentsdirectory, create one file with twoComponentsubclasses, e.g.:
from lfx.custom.custom_component.component import Component
from lfx.io import Output
from lfx.schema.data import Data
class First(Component):
display_name = "First"
name = "First"
outputs = [Output(display_name="Out", name="out", method="run")]
def run(self) -> Data:
return Data(data={"which": "First"})
class Second(Component):
display_name = "Second"
name = "Second"
outputs = [Output(display_name="Out2", name="out2", method="run2")]
def run2(self) -> Data:
return Data(data={"which": "Second"})
- Add a
Secondnode to a flow, save it, and reopen/rebuild the flow (or otherwise trigger a rebuild from the stored template rather than a fresh import — e.g. reloading the saved flow JSON viaGraph.from_payload). - The rebuilt node instantiates
First, notSecond— calling itsrun2/out2output raises an attribute error referencingFirst, not the class the node was configured as.
Expected behavior
Either:
- Reject a custom-components file containing more than one
Componentsubclass at load/validation time with a clear error naming the file and both classes, or - Select the class by the node's own stored
type/class-name metadata rather than "first class found in the source," so multiple components can safely share one file.
Who can help?
No response — filed from direct debugging, not familiar with current code ownership for lfx/custom.
Source: langflow-ai/langflow