Skip to content

Conversation

@ogabrielluiz
Copy link
Contributor

@ogabrielluiz ogabrielluiz commented Sep 25, 2025

Adds the check command which helps users check if their flows have outdated components and update them if they so choose.

Summary by CodeRabbit

  • New Features

    • Added a CLI command to check flows for outdated components, show diffs/breaking changes, and optionally update components (auto or interactive).
    • Run command now supports a --check option to prevent execution when outdated components are detected.
    • CLI displays help when no arguments are provided.
  • Chores

    • Simplified all starter project templates by removing dependency metadata from components, reducing noise and file size.

… function

- Removed the call to analyze_component_dependencies from the build_component_metadata function to improve load times.
- Added a TODO comment indicating the need to relocate this functionality to a more appropriate location in the codebase.

These changes enhance performance while maintaining the existing functionality for future implementation.
- Introduced a new CLI command to detect and update outdated components in JSON flows.
- Implemented functionality to load specific components based on flow metadata, check for outdated components using code hash comparison, and handle updates interactively or automatically.
- Enhanced logging for better debugging and user feedback during the update process.

This addition improves the usability of the framework by allowing users to maintain up-to-date components in their flows efficiently.
- Added an asynchronous function to check for outdated components in JSON flows before execution.
- Introduced a new CLI option to enable this check, enhancing user experience by ensuring components are up to date.
- Improved error handling to provide detailed feedback on outdated components found during the check.

This addition promotes better maintenance of flow components and prevents potential runtime issues.
- Introduced a new command in the Langflow CLI to check for outdated components in flows, enhancing user experience by ensuring components are current.
- Updated the CLI structure to include the check command alongside existing serve and run commands.
- Improved documentation for the new command to facilitate user understanding and usage.

This addition promotes better maintenance of flow components and aligns with recent enhancements for component checks.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds a new LFX CLI “check” command to inspect/update outdated components in flow JSONs and integrates a pre-run --check validation in the LFX run command. Langflow’s lfx sub-app now exposes the “check” command and defaults to help with no args. Starter project JSONs remove dependency metadata. Internal utils stop writing dependency metadata.

Changes

Cohort / File(s) Summary
LFX CLI: check feature and run pre-check
src/lfx/src/lfx/__main__.py, src/lfx/src/lfx/cli/check.py, src/lfx/src/lfx/cli/run.py
Introduces lfx check command, adds comprehensive flow-component analysis/update logic, and integrates optional pre-run component freshness check via --check.
Langflow CLI wiring
src/backend/base/langflow/__main__.py
Exposes lfx “check” subcommand and sets no_args_is_help=True for the lfx Typer app.
Starter projects JSON metadata cleanup
src/backend/base/langflow/initial_setup/starter_projects/*
Removes dependencies metadata blocks (and totals) across numerous starter project components; structure/modules unchanged. Affected files include: Basic Prompt Chaining.json, Basic Prompting.json, Blog Writer.json, Custom Component Generator.json, Document Q&A.json, Financial Report Parser.json, Hybrid Search RAG.json, Image Sentiment Analysis.json, Instagram Copywriter.json, Invoice Summarizer.json, Knowledge Ingestion.json, Knowledge Retrieval.json, Market Research.json, Meeting Summary.json, Memory Chatbot.json, News Aggregator.json, Nvidia Remix.json, Pokédex Agent.json, Portfolio Website Code Generator.json, Price Deal Finder.json, Research Agent.json, Research Translation Loop.json, SEO Keyword Generator.json, SaaS Pricing.json, Search agent.json, Sequential Tasks Agents.json, Simple Agent.json, Social Media Agent.json, Text Sentiment Analysis.json, Travel Planning Agents.json, Twitter Thread Generator.json, Vector Store RAG.json, Youtube Analysis.json.
Utils: dependency analyzer removal
src/lfx/src/lfx/custom/utils.py
Stops assigning runtime dependency analysis into component metadata; removes related import and code path.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant CLI as lfx CLI
  participant CK as check module
  participant ST as Settings/Components
  participant FS as Flow JSON

  U->>CLI: lfx check <flow.json> [--update|--force|-i|--output|--verbose]
  CLI->>CK: check_command_sync(...)
  CK->>FS: Load flow JSON
  CK->>ST: Load specific/all component types
  CK->>CK: Compare nodes vs latest templates<br/>• code hash/strings<br/>• inputs/outputs<br/>• diffs
  alt updates requested
    CK->>FS: Apply updates (auto/interactive)
    CK-->>U: Report outdated and updates, write output file
  else no updates
    CK-->>U: Report findings (counts, diffs)
  end
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant CLI as lfx run
  participant CK as check module
  participant EX as Executor

  U->>CLI: lfx run <flow.json> --check
  CLI->>CK: check_components_before_run(...)
  alt any outdated
    CK-->>CLI: Outdated list
    CLI-->>U: Error with per-component details
  else none outdated
    CK-->>CLI: OK
    CLI->>EX: Execute flow
    EX-->>U: Run result
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

size:L, lgtm

Suggested reviewers

  • jordanrfrazier
  • pedrocassalpacheco
  • mfortman11

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title Check ✅ Passed The title succinctly conveys the primary enhancement introduced by this pull request, namely enabling a check for outdated components within JSON flows, and it is directly related to the main changeset. It is clear, specific, and concise without extraneous information. It allows a reviewer scanning history to immediately understand the feature added.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the enhancement New feature or request label Sep 25, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Sep 25, 2025
@codecov
Copy link

codecov bot commented Sep 25, 2025

Codecov Report

❌ Patch coverage is 18.44262% with 398 lines in your changes missing coverage. Please review.
✅ Project coverage is 32.04%. Comparing base (f174556) to head (12de1f6).

Files with missing lines Patch % Lines
src/lfx/src/lfx/cli/check.py 15.75% 376 Missing and 9 partials ⚠️
src/lfx/src/lfx/cli/run.py 48.00% 12 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #9978      +/-   ##
==========================================
+ Coverage   31.62%   32.04%   +0.41%     
==========================================
  Files        1350     1351       +1     
  Lines       61154    61632     +478     
  Branches     9142     9265     +123     
==========================================
+ Hits        19341    19747     +406     
- Misses      40897    40948      +51     
- Partials      916      937      +21     
Flag Coverage Δ
backend 51.83% <100.00%> (+0.06%) ⬆️
frontend 13.59% <ø> (ø)
lfx 39.97% <17.93%> (+1.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/backend/base/langflow/__main__.py 54.90% <100.00%> (+0.09%) ⬆️
src/lfx/src/lfx/__main__.py 72.72% <100.00%> (+2.72%) ⬆️
src/lfx/src/lfx/custom/utils.py 28.63% <100.00%> (+4.26%) ⬆️
src/lfx/src/lfx/cli/run.py 71.90% <48.00%> (-1.96%) ⬇️
src/lfx/src/lfx/cli/check.py 15.75% <15.75%> (ø)

... and 30 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lfx/src/lfx/custom/utils.py (1)

599-605: Always set metadata (module, code_hash) — call build_component_metadata unconditionally

The pre-run/CLI check relies on metadata.module and metadata.code_hash. Here, metadata is only built when module_name is passed, which can lead to missing hashes/modules for other paths. Build metadata regardless; build_component_metadata already handles module_name=None.

-        if module_name:
-            frontend_node = build_component_metadata(
-                frontend_node, custom_component, module_name, custom_component.__class__.__name__
-            )
+        # Always build metadata. build_component_metadata handles module_name=None internally.
+        frontend_node = build_component_metadata(
+            frontend_node, custom_component, module_name, custom_component.__class__.__name__
+        )
🧹 Nitpick comments (2)
src/lfx/src/lfx/cli/check.py (1)

447-455: Wrong “header skip” in diff parsing (uses MAX_CONTEXT_BLOCKS)

MAX_CONTEXT_BLOCKS controls display, not the number of diff header lines. unified_diff produces 2 header lines; skip exactly 2 to avoid dropping content.

-    for line in diff_lines[MAX_CONTEXT_BLOCKS:]:  # Skip the file headers
+    for line in diff_lines[2:]:  # Skip the '---' and '+++' headers
src/lfx/src/lfx/cli/run.py (1)

42-45: Docstring “Raises” is inaccurate (ValueError, not typer.Exit)

The function raises ValueError on failures; update the docstring accordingly.

-    Raises:
-        typer.Exit: If outdated components are found or check fails
+    Raises:
+        ValueError: If outdated components are found or the check fails
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e9cf4c and 41aecbb.

📒 Files selected for processing (38)
  • src/backend/base/langflow/__main__.py (1 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Custom Component Generator.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Hybrid Search RAG.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Knowledge Ingestion.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Knowledge Retrieval.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Market Research.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Research Translation Loop.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Search agent.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Text Sentiment Analysis.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json (0 hunks)
  • src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json (0 hunks)
  • src/lfx/src/lfx/__main__.py (2 hunks)
  • src/lfx/src/lfx/cli/check.py (1 hunks)
  • src/lfx/src/lfx/cli/run.py (4 hunks)
  • src/lfx/src/lfx/custom/utils.py (2 hunks)
💤 Files with no reviewable changes (33)
  • src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json
  • src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json
  • src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json
  • src/backend/base/langflow/initial_setup/starter_projects/Research Translation Loop.json
  • src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json
  • src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json
  • src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json
  • src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json
  • src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json
  • src/backend/base/langflow/initial_setup/starter_projects/Custom Component Generator.json
  • src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json
  • src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json
  • src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json
  • src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json
  • src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json
  • src/backend/base/langflow/initial_setup/starter_projects/Knowledge Ingestion.json
  • src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json
  • src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json
  • src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json
  • src/backend/base/langflow/initial_setup/starter_projects/Text Sentiment Analysis.json
  • src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json
  • src/backend/base/langflow/initial_setup/starter_projects/Knowledge Retrieval.json
  • src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json
  • src/backend/base/langflow/initial_setup/starter_projects/Search agent.json
  • src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json
  • src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json
  • src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json
  • src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json
  • src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json
  • src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json
  • src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json
  • src/backend/base/langflow/initial_setup/starter_projects/Market Research.json
  • src/backend/base/langflow/initial_setup/starter_projects/Hybrid Search RAG.json
🧰 Additional context used
📓 Path-based instructions (1)
{src/backend/**/*.py,tests/**/*.py,Makefile}

📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)

{src/backend/**/*.py,tests/**/*.py,Makefile}: Run make format_backend to format Python code before linting or committing changes
Run make lint to perform linting checks on backend Python code

Files:

  • src/backend/base/langflow/__main__.py
🧬 Code graph analysis (5)
src/lfx/src/lfx/cli/run.py (1)
src/lfx/src/lfx/cli/check.py (1)
  • check_flow_components (145-236)
src/lfx/src/lfx/__main__.py (1)
src/lfx/src/lfx/cli/check.py (1)
  • check_command_sync (739-756)
src/lfx/src/lfx/custom/utils.py (1)
src/backend/base/langflow/api/v1/endpoints.py (1)
  • custom_component (666-684)
src/backend/base/langflow/__main__.py (1)
src/lfx/src/lfx/cli/check.py (1)
  • check_command_sync (739-756)
src/lfx/src/lfx/cli/check.py (2)
src/backend/base/langflow/services/deps.py (1)
  • get_settings_service (111-124)
src/lfx/src/lfx/custom/utils.py (1)
  • create_component_template (617-641)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (50)
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 36/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 40/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 39/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 37/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 35/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 38/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 34/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 14/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 31/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 33/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 30/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 29/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 32/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 27/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 23/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 28/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 24/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 19/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 26/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 20/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 25/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 22/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 16/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 21/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 12/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 17/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 18/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 13/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 15/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 7/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 6/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 10/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 11/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 8/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 9/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 5/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 3/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 4/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 2/40
  • GitHub Check: Run Frontend Tests / Playwright Tests - Shard 1/40
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 2
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 5
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 3
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 1
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 4
  • GitHub Check: Run Backend Tests / Integration Tests - Python 3.10
  • GitHub Check: Lint Backend / Run Mypy (3.12)
  • GitHub Check: Test Starter Templates
  • GitHub Check: Optimize new Python code in this PR
  • GitHub Check: test-starter-projects
🔇 Additional comments (3)
src/backend/base/langflow/__main__.py (1)

48-56: LGTM: lfx sub-app registers new “check” command

Import and registration look correct; no_args_is_help=True is consistent with existing sub-commands.

src/lfx/src/lfx/__main__.py (1)

5-19: LGTM: top-level LFX CLI exposes “check”

Command wiring and help text look good.

src/lfx/src/lfx/cli/run.py (1)

243-246: LGTM: pre-run check gate for JSON flows

Opt-in check before loading JSON flows is correctly scoped behind --check and surfaces actionable guidance.

Comment on lines 5 to 7
import difflib
from typing import Any

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid shared state mutation when updating nodes — deep-copy components

apply_component_update mutates nested fields of the component dict copied from all_types_dict. Shallow copy risks modifying the cached template. Import copy to perform deep copies.

-import difflib
+import difflib
+import copy
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import difflib
from typing import Any
import difflib
import copy
from typing import Any
🤖 Prompt for AI Agents
In src/lfx/src/lfx/cli/check.py around lines 5 to 7, the code currently imports
difflib and typing but uses shallow copies when updating component dicts, which
can mutate the cached templates in all_types_dict; import the copy module at the
top (add import copy) and update apply_component_update to replace any shallow
copy (e.g., dict(...) or .copy()) with copy.deepcopy(...) when copying component
data before mutating, ensuring nested fields are not shared with the cached
template.

Comment on lines 47 to 71
for module_path in component_modules:
try:
# Import the module and get the component class
module_parts = module_path.split(".")
module_name = ".".join(module_parts[:-1])
class_name = module_parts[-1]

await logger.adebug(f"Loading component {class_name} from {module_name}")

module = importlib.import_module(module_name)
component_class = getattr(module, class_name)

# Create component instance and template
component_instance = component_class()
comp_template, _ = create_component_template(
component_extractor=component_instance, module_name=module_path
)

# Use the class name as the component type
components_dict[class_name] = comp_template

except (ImportError, AttributeError, RuntimeError) as e:
await logger.awarning(f"Failed to load component {module_path}: {e}")
continue

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Unvalidated dynamic imports from flow metadata (RCE risk) and missing TypeError handling

Importing arbitrary module paths from flow JSON allows executing attacker‑controlled code during “check”. Restrict imports to trusted prefixes and handle constructor TypeError.

     for module_path in component_modules:
         try:
             # Import the module and get the component class
             module_parts = module_path.split(".")
             module_name = ".".join(module_parts[:-1])
             class_name = module_parts[-1]
 
+            # Security: only allow trusted component namespaces
+            allowed_prefixes = ("lfx.components.",)
+            if not module_name.startswith(allowed_prefixes):
+                await logger.awarning(
+                    f"Skipping untrusted module path '{module_path}'. "
+                    "Only 'lfx.components.*' modules are allowed for selective loading."
+                )
+                continue
+
+            if not module_name or not class_name:
+                await logger.awarning(f"Invalid component path '{module_path}'")
+                continue
+
             await logger.adebug(f"Loading component {class_name} from {module_name}")
 
             module = importlib.import_module(module_name)
             component_class = getattr(module, class_name)
 
             # Create component instance and template
-            component_instance = component_class()
+            try:
+                component_instance = component_class()
+            except TypeError as te:
+                await logger.awarning(
+                    f"Failed to instantiate component '{class_name}' from '{module_name}': {te}"
+                )
+                continue
             comp_template, _ = create_component_template(
                 component_extractor=component_instance, module_name=module_path
             )
 
             # Use the class name as the component type
             components_dict[class_name] = comp_template
 
-        except (ImportError, AttributeError, RuntimeError) as e:
+        except (ImportError, AttributeError, RuntimeError) as e:
             await logger.awarning(f"Failed to load component {module_path}: {e}")
             continue
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for module_path in component_modules:
try:
# Import the module and get the component class
module_parts = module_path.split(".")
module_name = ".".join(module_parts[:-1])
class_name = module_parts[-1]
await logger.adebug(f"Loading component {class_name} from {module_name}")
module = importlib.import_module(module_name)
component_class = getattr(module, class_name)
# Create component instance and template
component_instance = component_class()
comp_template, _ = create_component_template(
component_extractor=component_instance, module_name=module_path
)
# Use the class name as the component type
components_dict[class_name] = comp_template
except (ImportError, AttributeError, RuntimeError) as e:
await logger.awarning(f"Failed to load component {module_path}: {e}")
continue
for module_path in component_modules:
try:
# Import the module and get the component class
module_parts = module_path.split(".")
module_name = ".".join(module_parts[:-1])
class_name = module_parts[-1]
# Security: only allow trusted component namespaces
allowed_prefixes = ("lfx.components.",)
if not module_name.startswith(allowed_prefixes):
await logger.awarning(
f"Skipping untrusted module path '{module_path}'. "
"Only 'lfx.components.*' modules are allowed for selective loading."
)
continue
if not module_name or not class_name:
await logger.awarning(f"Invalid component path '{module_path}'")
continue
await logger.adebug(f"Loading component {class_name} from {module_name}")
module = importlib.import_module(module_name)
component_class = getattr(module, class_name)
# Create component instance and template
try:
component_instance = component_class()
except TypeError as te:
await logger.awarning(
f"Failed to instantiate component '{class_name}' from '{module_name}': {te}"
)
continue
comp_template, _ = create_component_template(
component_extractor=component_instance, module_name=module_path
)
# Use the class name as the component type
components_dict[class_name] = comp_template
except (ImportError, AttributeError, RuntimeError) as e:
await logger.awarning(f"Failed to load component {module_path}: {e}")
continue
🤖 Prompt for AI Agents
In src/lfx/src/lfx/cli/check.py around lines 47 to 71, the code performs
unvalidated dynamic imports from flow metadata (RCE risk) and does not handle
TypeError from component constructors; validate module_path before importing by
only allowing module_name and class_name that match a configurable
allowlist/prefixes (e.g., check module_name.startswith(...) or membership in an
allowed set) and reject/log and skip any module_path that fails validation,
after import verify the attribute is a class and optionally that it subclasses
the expected component base type before instantiation, wrap the
component_class() call in a try/except that also catches TypeError and logs an
awarning with the error, and keep the existing
ImportError/AttributeError/RuntimeError handling intact.

Comment on lines +76 to +107
def find_component_in_types(component_type: str, all_types_dict: dict) -> dict | None:
"""Find a component by searching across all categories in all_types_dict.
Args:
component_type: The component type to search for (e.g., 'Agent', 'CalculatorComponent')
all_types_dict: Dictionary of component categories and their components
Returns:
The component dict if found, None otherwise
"""
# First try direct lookup (for exact matches)
if component_type in all_types_dict:
return all_types_dict[component_type]

# Search across all categories
for category_components in all_types_dict.values():
if isinstance(category_components, dict):
# Look for exact component name match
if component_type in category_components:
return category_components[component_type]

# Look for component by display name or class name
for comp_name, comp_data in category_components.items():
if isinstance(comp_data, dict):
# Check display name
if comp_data.get("display_name") == component_type:
return comp_data
# Check if the component type matches any known mappings
if _matches_component_type(component_type, comp_name, comp_data):
return comp_data

return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

find_component_in_types fails for “flat” dicts from selective loads

When all_types_dict is a flat mapping {ComponentName: component_dict}, the current search wrongly treats component_dict as a “category” dict. Add a flat‑mapping search pass.

     # First try direct lookup (for exact matches)
     if component_type in all_types_dict:
         return all_types_dict[component_type]
 
+    # Handle flat mapping: { "AgentComponent": { ...component... }, ... }
+    # Detect by checking for component-like keys in values.
+    try:
+        for comp_name, comp_data in all_types_dict.items():
+            if isinstance(comp_data, dict) and ("template" in comp_data or "outputs" in comp_data):
+                if comp_data.get("display_name") == component_type or _matches_component_type(
+                    component_type, comp_name, comp_data
+                ):
+                    return comp_data
+    except AttributeError:
+        # Not a flat mapping; fall through to category search
+        pass
+
     # Search across all categories
     for category_components in all_types_dict.values():
         if isinstance(category_components, dict):
             # Look for exact component name match
             if component_type in category_components:
                 return category_components[component_type]
 
             # Look for component by display name or class name
             for comp_name, comp_data in category_components.items():
                 if isinstance(comp_data, dict):
                     # Check display name
                     if comp_data.get("display_name") == component_type:
                         return comp_data
                     # Check if the component type matches any known mappings
                     if _matches_component_type(component_type, comp_name, comp_data):
                         return comp_data
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def find_component_in_types(component_type: str, all_types_dict: dict) -> dict | None:
"""Find a component by searching across all categories in all_types_dict.
Args:
component_type: The component type to search for (e.g., 'Agent', 'CalculatorComponent')
all_types_dict: Dictionary of component categories and their components
Returns:
The component dict if found, None otherwise
"""
# First try direct lookup (for exact matches)
if component_type in all_types_dict:
return all_types_dict[component_type]
# Search across all categories
for category_components in all_types_dict.values():
if isinstance(category_components, dict):
# Look for exact component name match
if component_type in category_components:
return category_components[component_type]
# Look for component by display name or class name
for comp_name, comp_data in category_components.items():
if isinstance(comp_data, dict):
# Check display name
if comp_data.get("display_name") == component_type:
return comp_data
# Check if the component type matches any known mappings
if _matches_component_type(component_type, comp_name, comp_data):
return comp_data
return None
def find_component_in_types(component_type: str, all_types_dict: dict) -> dict | None:
"""Find a component by searching across all categories in all_types_dict.
Args:
component_type: The component type to search for (e.g., 'Agent', 'CalculatorComponent')
all_types_dict: Dictionary of component categories and their components
Returns:
The component dict if found, None otherwise
"""
# First try direct lookup (for exact matches)
if component_type in all_types_dict:
return all_types_dict[component_type]
# Handle flat mapping: { "AgentComponent": { ...component... }, ... }
# Detect by checking for component-like keys in values.
try:
for comp_name, comp_data in all_types_dict.items():
if isinstance(comp_data, dict) and ("template" in comp_data or "outputs" in comp_data):
if comp_data.get("display_name") == component_type or _matches_component_type(
component_type, comp_name, comp_data
):
return comp_data
except AttributeError:
# Not a flat mapping; fall through to category search
pass
# Search across all categories
for category_components in all_types_dict.values():
if isinstance(category_components, dict):
# Look for exact component name match
if component_type in category_components:
return category_components[component_type]
# Look for component by display name or class name
for comp_name, comp_data in category_components.items():
if isinstance(comp_data, dict):
# Check display name
if comp_data.get("display_name") == component_type:
return comp_data
# Check if the component type matches any known mappings
if _matches_component_type(component_type, comp_name, comp_data):
return comp_data
return None

Comment on lines 172 to 191
if component_modules:
await logger.adebug(f"Found {len(component_modules)} component modules in flow metadata, loading selectively")
all_types_dict = await load_specific_components(component_modules)
else:
await logger.adebug("No module metadata found, loading all component types")
settings_service = get_settings_service()
try:
all_types_dict = await get_and_cache_all_types_dict(settings_service)
# Log available component types for debugging
if all_types_dict:
available_types = list(all_types_dict.keys())
await logger.adebug(f"Loaded {len(available_types)} component types: {available_types[:10]}...")
else:
await logger.adebug("No component types loaded")
except (ImportError, RuntimeError) as e:
return {
"error": f"Failed to load component types: {e}",
"flow_path": flow_path,
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Selective load can under-detect; add fallback to full catalog

If selective loading yields zero components (e.g., bad/unknown module paths), the check silently proceeds with an empty catalog. Fall back to get_and_cache_all_types_dict.

     if component_modules:
         await logger.adebug(f"Found {len(component_modules)} component modules in flow metadata, loading selectively")
         all_types_dict = await load_specific_components(component_modules)
+        if not all_types_dict:
+            await logger.adebug(
+                "Selective component import returned no results; falling back to full catalog"
+            )
+            settings_service = get_settings_service()
+            try:
+                all_types_dict = await get_and_cache_all_types_dict(settings_service)
+            except (ImportError, RuntimeError) as e:
+                return {
+                    "error": f"Failed to load component types: {e}",
+                    "flow_path": flow_path,
+                }
     else:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if component_modules:
await logger.adebug(f"Found {len(component_modules)} component modules in flow metadata, loading selectively")
all_types_dict = await load_specific_components(component_modules)
else:
await logger.adebug("No module metadata found, loading all component types")
settings_service = get_settings_service()
try:
all_types_dict = await get_and_cache_all_types_dict(settings_service)
# Log available component types for debugging
if all_types_dict:
available_types = list(all_types_dict.keys())
await logger.adebug(f"Loaded {len(available_types)} component types: {available_types[:10]}...")
else:
await logger.adebug("No component types loaded")
except (ImportError, RuntimeError) as e:
return {
"error": f"Failed to load component types: {e}",
"flow_path": flow_path,
}
if component_modules:
await logger.adebug(
f"Found {len(component_modules)} component modules in flow metadata, loading selectively"
)
all_types_dict = await load_specific_components(component_modules)
if not all_types_dict:
await logger.adebug(
"Selective component import returned no results; falling back to full catalog"
)
settings_service = get_settings_service()
try:
all_types_dict = await get_and_cache_all_types_dict(settings_service)
except (ImportError, RuntimeError) as e:
return {
"error": f"Failed to load component types: {e}",
"flow_path": flow_path,
}
else:
await logger.adebug("No module metadata found, loading all component types")
settings_service = get_settings_service()
try:
all_types_dict = await get_and_cache_all_types_dict(settings_service)
# Log available component types for debugging
if all_types_dict:
available_types = list(all_types_dict.keys())
await logger.adebug(
f"Loaded {len(available_types)} component types: {available_types[:10]}..."
)
else:
await logger.adebug("No component types loaded")
except (ImportError, RuntimeError) as e:
return {
"error": f"Failed to load component types: {e}",
"flow_path": flow_path,
}
🤖 Prompt for AI Agents
In src/lfx/src/lfx/cli/check.py around lines 172 to 191, when component_modules
is provided but load_specific_components returns an empty dict the code
currently continues with an empty catalog; change the flow so that after calling
load_specific_components you check if all_types_dict is empty/falsy and, if so,
obtain settings_service and call get_and_cache_all_types_dict to fall back to
the full catalog (log the fallback with logger.adebug), preserving the existing
try/except behavior for get_and_cache_all_types_dict and returning the same
error structure on exceptions.

Comment on lines 632 to 641
# Update the node with the latest component data
node["data"]["node"] = found_component.copy()

# Restore user values where possible
updated_template = node["data"]["node"].get("template", {})
for field_name, user_value in user_values.items():
if field_name in updated_template and isinstance(updated_template[field_name], dict):
updated_template[field_name]["value"] = user_value

break
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Deep copy updated component to prevent mutating the catalog

Shallow copying found_component means nested template dicts are shared with all_types_dict. Mutating 'value' will leak back into the catalog.

-            node["data"]["node"] = found_component.copy()
+            node["data"]["node"] = copy.deepcopy(found_component)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Update the node with the latest component data
node["data"]["node"] = found_component.copy()
# Restore user values where possible
updated_template = node["data"]["node"].get("template", {})
for field_name, user_value in user_values.items():
if field_name in updated_template and isinstance(updated_template[field_name], dict):
updated_template[field_name]["value"] = user_value
break
# Update the node with the latest component data
node["data"]["node"] = copy.deepcopy(found_component)
# Restore user values where possible
updated_template = node["data"]["node"].get("template", {})
for field_name, user_value in user_values.items():
if field_name in updated_template and isinstance(updated_template[field_name], dict):
updated_template[field_name]["value"] = user_value
break
🤖 Prompt for AI Agents
In src/lfx/src/lfx/cli/check.py around lines 632 to 641, the code uses a shallow
copy (found_component.copy()) when assigning node["data"]["node"], which allows
nested template dicts to remain shared with the catalog; change this to a deep
copy to avoid mutating shared structures (use copy.deepcopy(found_component))
and ensure the copy module is imported at the top of the file (import copy) so
subsequent updates to template[field]["value"] do not leak back into
all_types_dict.

Comment on lines 528 to 539
# TODO: Move this to the place where it is needed
# try:
# dependency_info = analyze_component_dependencies(custom_component._code)
# frontend_node.metadata["dependencies"] = dependency_info
# except (SyntaxError, TypeError, ValueError, ImportError) as exc:
# logger.warning(f"Failed to analyze dependencies for component {ctype_name}: {exc}")
# # Set minimal dependency info on failure
# frontend_node.metadata["dependencies"] = {
# "total_dependencies": 0,
# "dependencies": [],
# }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Dependencies metadata removed — verify no consumers require metadata['dependencies']

You’ve commented out dependency analysis and no longer write metadata['dependencies']. Ensure no code paths or UI expect it to exist to avoid KeyErrors or feature regressions.

Run this repo-wide scan to confirm no hard dependency on metadata['dependencies'] remains:


🏁 Script executed:

#!/bin/bash
# Search for accesses to metadata["dependencies"] or .get("dependencies")
rg -n -C2 -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' \
  -P 'metadata\s*[\[\.]\s*["'\'']dependencies["'\'']|\.get\(\s*["'\'']dependencies["'\'']'

Length of output: 148


#!/bin/bash
# Search for literal metadata["dependencies"] or metadata['dependencies']
rg -F -n 'metadata["dependencies"]'
rg -F -n "metadata['dependencies']"
# Search for literal metadata.get("dependencies") or metadata.get('dependencies')
rg -F -n 'metadata.get("dependencies")'
rg -F -n "metadata.get('dependencies')"

🏁 Script executed:

#!/bin/bash
# Search for direct dict access metadata['dependencies'] in all Python files
rg --type py -n -H -C1 "metadata\[\s*['\"]dependencies['\"]\s*\]"
# Search for dict.get('dependencies')
rg --type py -n -H -C1 "metadata\.get\(\s*['\"]dependencies['\"]\s*\)"

Length of output: 2301


Restore or stub metadata['dependencies'] usage
Tests in src/lfx/tests/unit/custom/test_utils_metadata.py (e.g. lines 375, 398, 425, 460, 512) still reference metadata["dependencies"], so commenting out its assignment breaks CI. Re-enable at least a stub assignment in utils.py or update tests to use metadata.get("dependencies", …).

🤖 Prompt for AI Agents
In src/lfx/src/lfx/custom/utils.py around lines 528-539, the assignment to
frontend_node.metadata["dependencies"] was commented out causing tests to fail;
restore the logic so metadata["dependencies"] is always set: wrap the dependency
analysis in a try/except and on success set metadata["dependencies"] to the
analyzed info, and on any failure set a minimal stub like {"total_dependencies":
0, "dependencies": []} (and log the warning). Ensure the except catches the same
exceptions (SyntaxError, TypeError, ValueError, ImportError) and that the
metadata key is assigned in both branches.

- Introduced comprehensive unit tests for the check command, covering various scenarios including component detection, outdated checks, and error handling.
- Implemented tests for both synchronous and asynchronous functions, ensuring robust validation of the check command's behavior.
- Enhanced test coverage for component mapping and breaking change detection, contributing to improved reliability and maintainability of the codebase.
- Included tests for integration with real flow files, ensuring the command operates correctly in practical scenarios.

These additions strengthen the testing framework and promote confidence in the check command's functionality.
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Sep 25, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Sep 25, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Sep 25, 2025
- Adjusted the `generate_code_diff` function to skip the correct headers in the diff output, enhancing accuracy in code comparison.
- Replaced shallow copy with deep copy for component updates in `apply_component_update`, ensuring that nested structures are correctly preserved during updates. This change improves data integrity and prevents unintended mutations.
- Updated the `build_component_metadata` function to always initialize the `dependencies` field in the frontend node's metadata with default values, enhancing consistency and preventing potential issues when analysis is disabled.
- Added `@pytest.mark.xfail` to tests for building component metadata with external, optional, and real dependencies, indicating that the dependency analyzer is currently commented out and returns minimal stub data. This change helps clarify the expected behavior during testing and maintains test integrity until the analyzer is fully implemented.
- Added security checks to ensure only trusted component namespaces are loaded, preventing potential vulnerabilities.
- Implemented error handling for component instantiation failures, logging warnings for invalid paths and instantiation issues to improve robustness and traceability during component loading.
…unction

- Introduced a new test suite for the `load_specific_components` function to validate security measures against various attack vectors, including blocking malicious module imports and handling invalid paths.
- Ensured that both valid and invalid component paths are processed correctly, maintaining robust security validation.
- Implemented tests for case sensitivity and strict prefix validation to prevent potential bypass attempts.
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Sep 25, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Nov 21, 2025

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 15%
14.68% (3985/27139) 7.56% (1560/20608) 8.94% (535/5984)

Unit Test Results

Tests Skipped Failures Errors Time
1630 0 💤 0 ❌ 0 🔥 19.276s ⏱️

# Conflicts:
#	src/lfx/src/lfx/_assets/component_index.json
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Nov 21, 2025
Copy link
Collaborator

@jordanrfrazier jordanrfrazier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good to me. if it's tested and working manually then lgtm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants