Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/changes-filter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ python:
- "src/backend/**"
- "src/backend/**.py"
- "src/lfx/**"
- "src/langflow-core/**"
- "pyproject.toml"
- "uv.lock"
- "src/backend/base/pyproject.toml"
Expand Down Expand Up @@ -34,7 +35,9 @@ docker:
- "src/backend/**"
- "src/frontend/**"
- "src/lfx/**"
- "src/langflow-core/**"
- ".dockerignore"
- ".github/workflows/docker-build-v2.yml"
- ".github/workflows/docker_test.yml"

# Test categories and their associated paths
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-scripts-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
python-version: "3.13"

- name: Install dependencies
run: pip install pytest requests packaging
run: pip install orjson packaging pytest requests

- name: Run CI script tests
run: python -m pytest scripts/ci/ -v
218 changes: 211 additions & 7 deletions .github/workflows/cross-platform-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ on:
inputs:
base-artifact-name:
description: "Name of the base package artifact"
required: true
required: false
type: string
core-artifact-name:
description: "Name of the langflow-core package artifact"
required: false
type: string
main-artifact-name:
description: "Name of the main package artifact"
required: true
required: false
type: string
lfx-artifact-name:
description: "Name of the LFX package artifact"
Expand All @@ -43,6 +47,7 @@ jobs:
if: inputs.langflow-version == '' && contains(github.workflow_ref, 'cross-platform-test.yml')
outputs:
base-artifact-name: ${{ steps.set-names.outputs.base-artifact-name }}
core-artifact-name: ${{ steps.set-names.outputs.core-artifact-name }}
main-artifact-name: ${{ steps.set-names.outputs.main-artifact-name }}
lfx-artifact-name: ${{ steps.set-names.outputs.lfx-artifact-name }}
sdk-artifact-name: ${{ steps.set-names.outputs.sdk-artifact-name }}
Expand All @@ -69,6 +74,8 @@ jobs:
# Base package builds to dist/ but should be in src/backend/base/dist/
mkdir -p src/backend/base/dist
mv dist/langflow_base*.whl src/backend/base/dist/
- name: Build core package
run: make build_langflow_core args="--wheel"
- name: Build LFX package
run: |
cd src/lfx
Expand Down Expand Up @@ -113,6 +120,11 @@ jobs:
with:
name: adhoc-dist-base
path: src/backend/base/dist
- name: Upload core artifact
uses: actions/upload-artifact@v6
with:
name: adhoc-dist-core
path: src/langflow-core/dist
- name: Upload main artifact
uses: actions/upload-artifact@v6
with:
Expand All @@ -128,6 +140,7 @@ jobs:
id: set-names
run: |
echo "base-artifact-name=adhoc-dist-base" >> $GITHUB_OUTPUT
echo "core-artifact-name=adhoc-dist-core" >> $GITHUB_OUTPUT
echo "main-artifact-name=adhoc-dist-main" >> $GITHUB_OUTPUT
echo "lfx-artifact-name=adhoc-dist-lfx" >> $GITHUB_OUTPUT
echo "sdk-artifact-name=adhoc-dist-sdk" >> $GITHUB_OUTPUT
Expand All @@ -140,7 +153,10 @@ jobs:
test-installation-stable:
name: Install & Run - ${{ matrix.os }} ${{ matrix.arch }} ${{ matrix.python-version }}
needs: [build-if-needed]
if: always() && (needs.build-if-needed.result == 'success' || needs.build-if-needed.result == 'skipped')
if: |
always() &&
(needs.build-if-needed.result == 'success' || needs.build-if-needed.result == 'skipped') &&
(inputs.langflow-version != '' || inputs.main-artifact-name != '' || needs.build-if-needed.outputs.main-artifact-name != '')
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
Expand Down Expand Up @@ -257,7 +273,7 @@ jobs:
path: ./base-dist

- name: Download main package artifact
if: steps.install-method.outputs.method == 'wheel'
if: steps.install-method.outputs.method == 'wheel' && (inputs.main-artifact-name != '' || needs.build-if-needed.outputs.main-artifact-name != '')
uses: actions/download-artifact@v7
with:
name: ${{ inputs.main-artifact-name || needs.build-if-needed.outputs.main-artifact-name || 'adhoc-dist-main' }}
Expand Down Expand Up @@ -464,7 +480,10 @@ jobs:
test-installation-experimental:
name: Install & Run - ${{ matrix.os }} ${{ matrix.arch }} ${{ matrix.python-version }} (Experimental)
needs: [build-if-needed]
if: always() && (needs.build-if-needed.result == 'success' || needs.build-if-needed.result == 'skipped')
if: |
always() &&
(needs.build-if-needed.result == 'success' || needs.build-if-needed.result == 'skipped') &&
(inputs.langflow-version != '' || inputs.main-artifact-name != '' || needs.build-if-needed.outputs.main-artifact-name != '')
runs-on: ${{ matrix.runner }}
continue-on-error: true
strategy:
Expand Down Expand Up @@ -553,7 +572,7 @@ jobs:
path: ./base-dist

- name: Download main package artifact
if: steps.install-method.outputs.method == 'wheel'
if: steps.install-method.outputs.method == 'wheel' && (inputs.main-artifact-name != '' || needs.build-if-needed.outputs.main-artifact-name != '')
uses: actions/download-artifact@v7
with:
name: ${{ inputs.main-artifact-name || needs.build-if-needed.outputs.main-artifact-name || 'adhoc-dist-main' }}
Expand Down Expand Up @@ -889,19 +908,204 @@ jobs:
"
shell: bash

test-core-runtime:
name: Core Distribution Wheel - Linux Python 3.14

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not make experimental Python 3.14 the blocking core release gate.

The workflow explicitly treats Python 3.14 as non-blocking elsewhere, but this job makes its dependency compatibility mandatory for release. Run the blocking core check on a stable version such as 3.13; retain 3.14 separately as experimental coverage.

Proposed fix
-    name: Core Distribution Wheel - Linux Python 3.14
+    name: Core Distribution Wheel - Linux Python 3.13
...
-          python-version: "3.14"
+          python-version: "3.13"
...
-          uv venv core-test-env --seed --python 3.14
+          uv venv core-test-env --seed --python 3.13

Also applies to: 923-927, 989-993, 1100-1103

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cross-platform-test.yml at line 912, Update the core
distribution wheel jobs identified by their names and corresponding
Python-version configuration to use stable Python 3.13 for the blocking release
checks instead of experimental 3.14. Preserve separate Python 3.14 jobs as
non-blocking experimental coverage, including the additional affected job
sections.

needs: [build-if-needed]
if: |
Comment on lines +913 to +914

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Top-level workflow permissions and job context\n'
rg -n --no-heading '^(permissions:|  test-core-runtime:|    permissions:|    needs:|    if:|    steps:|    uses:|    runs-on:)' .github/workflows/cross-platform-test.yml

printf '\n## Nearby lines around the cited job\n'
sed -n '900,960p' .github/workflows/cross-platform-test.yml

printf '\n## Any explicit permissions blocks in this workflow\n'
rg -n --no-heading '^(\s*)permissions:' .github/workflows/cross-platform-test.yml

Repository: langflow-ai/langflow

Length of output: 3471


🌐 Web query:

GitHub Actions download-artifact permissions actions:read contents:read official documentation

💡 Result:

When using the actions/download-artifact action, the required permissions depend on whether you are downloading artifacts from the current workflow run or from a different context [1][2]. 1. Current Workflow Run: By default, the action does not require a specific token input to download artifacts from the same repository and current workflow run [1][3]. In this scenario, it uses the job's default permissions [4]. 2. Different Workflow Run or Repository: When downloading artifacts from a different repository or a different workflow run, you must provide a github-token input [2][3]. This token must have actions:read permissions on the target repository [2][5]. A Personal Access Token (PAT) with these permissions is typically used for this purpose [4]. Configuration Example: To download an artifact from a different repository or run, include the github-token and relevant identifiers in your workflow YAML: steps: - uses: actions/download-artifact@v4 with: name: my-other-artifact github-token: ${{ secrets.GH_PAT }} # Token with actions:read permissions on the target repo [2][3] repository: owner/repo-name run-id: 123456789 Note that if you are using the GITHUB_TOKEN for tasks within the same repository, ensure your workflow job has the appropriate permissions defined if you are using granular permissions [6]: permissions: actions: read contents: read

Citations:


Restrict this job’s token scope. It only downloads artifacts and runs local validation, so add an explicit least-privilege block; for actions/download-artifact with granular GITHUB_TOKEN permissions, include both actions: read and contents: read.

Proposed fix
  test-core-runtime:
    name: Core Distribution Wheel - Linux Python 3.14
    needs: [build-if-needed]
+   permissions:
+     actions: read
+     contents: read
📝 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
needs: [build-if-needed]
if: |
needs: [build-if-needed]
permissions:
actions: read
contents: read
if: |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cross-platform-test.yml around lines 913 - 914, Add an
explicit least-privilege permissions block to the job using needs:
[build-if-needed], granting only actions: read and contents: read for artifact
downloads and local validation.

Source: Linters/SAST tools

always() &&
inputs.langflow-version == '' &&
(needs.build-if-needed.result == 'success' || needs.build-if-needed.result == 'skipped') &&
(inputs.base-artifact-name != '' || inputs.core-artifact-name != '' ||
needs.build-if-needed.outputs.base-artifact-name != '' ||
needs.build-if-needed.outputs.core-artifact-name != '')
runs-on: ubuntu-latest
steps:
- name: Setup UV
uses: astral-sh/setup-uv@v6
with:
enable-cache: false
python-version: "3.14"

- name: Download langflow-sdk package artifact
if: inputs.sdk-artifact-name != '' || needs.build-if-needed.outputs.sdk-artifact-name != ''
uses: actions/download-artifact@v7
with:
name: ${{ inputs.sdk-artifact-name || needs.build-if-needed.outputs.sdk-artifact-name || 'adhoc-dist-sdk' }}
path: ./sdk-dist

- name: Download LFX package artifact
if: inputs.lfx-artifact-name != '' || needs.build-if-needed.outputs.lfx-artifact-name != ''
uses: actions/download-artifact@v7
with:
name: ${{ inputs.lfx-artifact-name || needs.build-if-needed.outputs.lfx-artifact-name || 'adhoc-dist-lfx' }}
path: ./lfx-dist

- name: Download base package artifact
if: inputs.base-artifact-name != '' || needs.build-if-needed.outputs.base-artifact-name != ''
uses: actions/download-artifact@v7
with:
name: ${{ inputs.base-artifact-name || needs.build-if-needed.outputs.base-artifact-name || 'adhoc-dist-base' }}
path: ./base-dist

- name: Download langflow-core package artifact
if: inputs.core-artifact-name != '' || needs.build-if-needed.outputs.core-artifact-name != ''
uses: actions/download-artifact@v7
with:
name: ${{ inputs.core-artifact-name || needs.build-if-needed.outputs.core-artifact-name || 'adhoc-dist-core' }}
path: ./core-dist

- name: Install only core wheels
shell: bash
run: |
set -euo pipefail
shopt -s nullglob

SDK_WHEELS=(./sdk-dist/*.whl)
LFX_WHEELS=(./lfx-dist/*.whl)
BASE_WHEELS=(./base-dist/*.whl)
CORE_WHEELS=(./core-dist/*.whl)
[ ${#SDK_WHEELS[@]} -le 1 ] || { echo "Expected at most one SDK wheel, found ${#SDK_WHEELS[@]}"; exit 1; }
[ ${#LFX_WHEELS[@]} -le 1 ] || { echo "Expected at most one LFX wheel, found ${#LFX_WHEELS[@]}"; exit 1; }
[ ${#BASE_WHEELS[@]} -le 1 ] || { echo "Expected at most one base wheel, found ${#BASE_WHEELS[@]}"; exit 1; }
[ ${#CORE_WHEELS[@]} -le 1 ] || { echo "Expected at most one core wheel, found ${#CORE_WHEELS[@]}"; exit 1; }

FIND_LINKS=()
for directory in sdk-dist lfx-dist base-dist core-dist; do
if [ -d "$directory" ]; then
FIND_LINKS+=(--find-links "./$directory")
fi
done
if [ ${#CORE_WHEELS[@]} -eq 1 ]; then
INSTALL_ROOTS=("${CORE_WHEELS[0]}")
echo "EXPECT_LANGFLOW_CORE=true" >> "$GITHUB_ENV"
elif [ ${#BASE_WHEELS[@]} -eq 1 ]; then
INSTALL_ROOTS=("${BASE_WHEELS[0]}")
echo "EXPECT_LANGFLOW_CORE=false" >> "$GITHUB_ENV"
else
echo "A base or langflow-core wheel is required for the core runtime test."
exit 1
fi

uv venv core-test-env --seed --python 3.14
uv pip install --python core-test-env/bin/python \
--prerelease=if-necessary-or-explicit \
"${FIND_LINKS[@]}" "${SDK_WHEELS[@]}" "${LFX_WHEELS[@]}" "${INSTALL_ROOTS[@]}"
uv pip check --python core-test-env/bin/python

- name: Verify core boundary and indexed imports
shell: bash
run: |
core-test-env/bin/python - <<'PY'
import importlib
import os
import re
from importlib.metadata import distributions, entry_points

from lfx.interface.components import _read_component_index

normalize = lambda value: re.sub(r"[-_.]+", "-", value).lower()
installed = {normalize(dist.metadata["Name"]) for dist in distributions() if dist.metadata["Name"]}
assert {"langflow-base", "langflow-sdk", "lfx"} <= installed, installed
assert "langflow" not in installed, installed
if os.environ["EXPECT_LANGFLOW_CORE"] == "true":
assert "langflow-core" in installed, installed
else:
assert "langflow-core" not in installed, installed
extensions = sorted(name for name in installed if name.startswith("lfx-"))
assert not extensions, f"Extension distributions installed: {extensions}"
for group in ("lfx.bundles", "langflow.extensions", "langflow.plugins"):
assert not entry_points(group=group), f"Extension entry points registered in {group}"

index = _read_component_index()
assert index is not None, "Installed LFX rejected its bundled component index"
assert index["metadata"] == {"num_modules": 17, "num_components": 130}
for category, components in index["entries"]:
for component_name, component in components.items():
module_path = component.get("metadata", {}).get("module")
assert module_path, f"{category}.{component_name} has no module"
module_name, class_name = module_path.rsplit(".", 1)
getattr(importlib.import_module(module_name), class_name)
PY

- name: Boot installed core server
shell: bash
timeout-minutes: 5
run: |
set -euo pipefail
RUNTIME_DIR=$(mktemp -d)
export LANGFLOW_CONFIG_DIR="$RUNTIME_DIR/config"
export LANGFLOW_SAVE_DB_IN_CONFIG_DIR=true
export LANGFLOW_AUTO_LOGIN=true
export DO_NOT_TRACK=true
LOG_FILE="$RUNTIME_DIR/server.log"

if [ "$EXPECT_LANGFLOW_CORE" = "true" ]; then
LANGFLOW_CLI=core-test-env/bin/langflow
else
LANGFLOW_CLI=core-test-env/bin/langflow-base
fi
"$LANGFLOW_CLI" run \
--host 127.0.0.1 --port 7861 --backend-only --workers 1 >"$LOG_FILE" 2>&1 &
Comment on lines +1042 to +1048

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Exercise the new langflow-core CLI.

The test currently validates only the compatibility langflow entry point. Boot through langflow-core and separately check the alias so either missing entry point fails CI.

Proposed fix
           if [ "$EXPECT_LANGFLOW_CORE" = "true" ]; then
-            LANGFLOW_CLI=core-test-env/bin/langflow
+            core-test-env/bin/langflow --help >/dev/null
+            LANGFLOW_CLI=core-test-env/bin/langflow-core
           else
             LANGFLOW_CLI=core-test-env/bin/langflow-base
           fi
📝 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 [ "$EXPECT_LANGFLOW_CORE" = "true" ]; then
LANGFLOW_CLI=core-test-env/bin/langflow
else
LANGFLOW_CLI=core-test-env/bin/langflow-base
fi
"$LANGFLOW_CLI" run \
--host 127.0.0.1 --port 7861 --backend-only --workers 1 >"$LOG_FILE" 2>&1 &
if [ "$EXPECT_LANGFLOW_CORE" = "true" ]; then
core-test-env/bin/langflow --help >/dev/null
LANGFLOW_CLI=core-test-env/bin/langflow-core
else
LANGFLOW_CLI=core-test-env/bin/langflow-base
fi
"$LANGFLOW_CLI" run \
--host 127.0.0.1 --port 7861 --backend-only --workers 1 >"$LOG_FILE" 2>&1 &
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cross-platform-test.yml around lines 1042 - 1048, Update
the CLI startup logic in the EXPECT_LANGFLOW_CORE branch to launch and validate
the langflow-core entry point, while separately checking the compatibility
langflow alias. Ensure CI fails when either entry point is missing, and preserve
the existing langflow-base path for non-core runs.

SERVER_PID=$!
cleanup() {
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
}
trap cleanup EXIT

READY=false
for _ in $(seq 1 90); do
if curl -fsS http://127.0.0.1:7861/health_check >/dev/null 2>&1; then
READY=true
break
fi
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
cat "$LOG_FILE"
exit 1
fi
sleep 2
done
if [ "$READY" != true ]; then
cat "$LOG_FILE"
exit 1
fi

curl -fsS -c "$RUNTIME_DIR/cookies" http://127.0.0.1:7861/api/v1/auto_login >/dev/null
curl -fsS --compressed -b "$RUNTIME_DIR/cookies" \
http://127.0.0.1:7861/api/v1/all >"$RUNTIME_DIR/catalog.json"
jq -e 'del(.component_display_names) | (length == 17 and ([.[] | length] | add) == 130)' \
"$RUNTIME_DIR/catalog.json"

test-summary:
name: Cross-Platform Test Summary
needs: [test-installation-stable, test-installation-experimental]
needs: [test-installation-stable, test-installation-experimental, test-core-runtime]
runs-on: ubuntu-latest
if: always()
steps:
- name: Check test results
run: |
stable_result="${{ needs.test-installation-stable.result }}"
experimental_result="${{ needs.test-installation-experimental.result }}"
core_result="${{ needs.test-core-runtime.result }}"

echo "Stable platforms result: $stable_result"
echo "Experimental platforms result: $experimental_result"
echo "Core runtime result: $core_result"

if [ "$stable_result" = "skipped" ] && [ "$core_result" = "skipped" ]; then
echo "✅ No Langflow application distribution was selected; package build tests passed"
exit 0
fi

if [ "${{ inputs.langflow-version }}" = "" ] && [ "$core_result" != "success" ]; then
Comment on lines 1085 to +1100

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant workflow section with line numbers
sed -n '1040,1120p' .github/workflows/cross-platform-test.yml | cat -n

printf '\n---\n'

# Inspect the workflow triggers and input declarations
rg -n "workflow_call|inputs:|langflow-version|on:" .github/workflows/cross-platform-test.yml

printf '\n---\n'

# Show the top of the file for reusable-workflow context
sed -n '1,120p' .github/workflows/cross-platform-test.yml | cat -n

Repository: langflow-ai/langflow

Length of output: 12233


Avoid expanding the workflow input directly in the shell. ${{ inputs.langflow-version }} is user-controlled and can break out of the quoted test; pass it through env and compare "$LANGFLOW_VERSION" instead.

🧰 Tools
🪛 zizmor (1.26.1)

[error] 1100-1100: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cross-platform-test.yml around lines 1085 - 1100, Update
the “Check test results” step to pass the langflow-version input through the
step’s env configuration, then compare the resulting LANGFLOW_VERSION variable
in the shell condition instead of expanding inputs.langflow-version directly.
Preserve the existing empty-value and core-result logic.

Source: Linters/SAST tools

echo "❌ Core runtime wheel test failed: $core_result"
exit 1
fi

if [ "$stable_result" = "skipped" ] && [ "$core_result" = "success" ]; then
echo "✅ Core-only wheel installation tests passed"
exit 0
fi

# Stable platforms must succeed, experimental can fail
if [ "$stable_result" = "success" ]; then
Expand Down
Loading
Loading