When ci.yml is called from release.yml (inputs.release == true), a
Biome failure was blocking all downstream publish and docker jobs via
needs.ci.result == 'success'. Biome was already excluded from the
ci_success gate for PR merges, but the reusable CI workflow itself
could still fail when the lint job errored.
Add continue-on-error: ${{ inputs.release == true }} to lint-frontend
so Biome failures are informational-only during release runs, while
remaining visible (and blocking ci_success) on regular PRs.
* fix(bundles): bump lfx-* bundles to 0.1.1 to republish with corrected lfx pin
The published 0.1.0 artifacts on PyPI carry stale lfx pins from before the lfx 0.5.0->1.10.0 version realignment:
- lfx-arxiv, lfx-duckduckgo: lfx>=0.5.0,<0.6.0 (hard-broken; the <0.6.0 cap can never resolve lfx 1.10.0)
- lfx-docling, lfx-ibm: lfx>=0.5.0 (uncapped floor/cap mismatch)
The source pin was already corrected to lfx>=1.10.0,<2.0.0 in #13516, but the bundles were never re-published. PyPI versions are immutable, so shipping the fix requires a version bump. Bump all four to 0.1.1 so the Release Bundles workflow cuts fresh wheels carrying the correct pin. Root pyproject keeps its >=0.1.0 floors (satisfied by 0.1.1); only the bundle dist versions and their uv.lock stamps change.
* fix(release): relax bundle lfx floor for pre-release builds + idempotent bundle publish
The RC pre-release run builds lfx as 1.10.0rc0, but bundles floor lfx at >=1.10.0. Under PEP 440 a pre-release sorts below the final, so 1.10.0rc0 fails >=1.10.0 and the cross-platform install test cannot resolve the bundle wheels against the RC lfx wheel.
build-base/build-main/build-lfx already rewrite their inter-package deps to the pre-release version when pre_release=true; build-bundles was the only release artifact missing that step. Add it: when pre_release=true, rewrite each bundle's lfx floor to the exact pre-release version, keeping the wide <2.0.0 BUNDLE_API cap. Stable source stays at >=1.10.0 -- only RC wheels are relaxed, at build time, so no source churn or re-tag.
Also make publish-bundles tolerate 'already exists' duplicate wheels on rerun, matching release_bundles.yml.
* fix(security): block code-execution components on unauthenticated public flow builds (H1-3754930)
PythonCodeStructuredTool exec()'d its attacker-controlled `tool_code` field at
flow-build time. Because a PUBLIC flow can be built with no authentication via
POST /api/v1/build_public_tmp/{flow_id}/flow, that sink was reachable as an
unauthenticated server-side RCE — and other components (Python Interpreter/REPL,
Smart Transform) execute code on the same path, so removing one component alone
would not close the gap.
- Harden the public build path: build_public_tmp now rejects flows containing
code-execution components via validate_public_flow_no_code_execution(). The
check keys on the node `type`, so it holds regardless of the stored component
`code`, and is enforced ONLY on the unauthenticated public path —
authenticated /build is unchanged.
- Neuter PythonCodeStructuredTool to a non-executable compatibility stub: all
exec()/eval() sinks removed; build_tool returns a tool that raises a
deprecation error. The component stays registered with identical
display_name/inputs so saved flows still load and locale keys don't change.
It will be fully removed in a future release.
component_index.json (code_hash) is regenerated by autofix.ci.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* fix: validate_public_flow_no_code_execution
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix: serialize DataFrames in tool mode to prevent pandas truncation
When Memory Base or Knowledge components are wired as agent tools, the DataFrame
result was returned directly without serialization. LangChain would then stringify
this for the agent observation using pandas' default repr, which truncates all
cells to 50 characters (display.max_colwidth=50), inserting "...".
This breaks the F3 agent use case where agents need to reliably recall facts from
memory. The agent receives truncated content and cannot see the full text.
The fix serializes DataFrames through the existing serialize() function, which
converts them to list[dict] format with full, untruncated content. This maintains
consistency with how other result types (Message, Data, etc.) are handled in tool mode.
- Affects: Memory Base and Knowledge components used as agent tools
- Does not affect: Component-to-component wiring or normal (non-tool) execution
- Testing: Added tests verifying DataFrames serialize to list[dict] with complete content
Improved MB GUI component description
* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]
* [autofix.ci] apply automated fixes
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* refactor: remove getenvvar component and update locales
* test: add test for GetEnvVar component removal and update component index
---------
Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local>
fix: use shadcn canvas colors for playground code blocks and chat-bg for the container
Standardize the playground tool-call surfaces on shadcn theme tokens so
they swap correctly between light and dark mode:
- Code blocks (SimplifiedCodeTabComponent) use bg-canvas: light grey
(#F4F4F5) / dark black (#000) so they stand out as the code surface.
- The 'Called tool' container (ContentBlockDisplay) uses bg-background so
it matches the chat background in both themes instead of inverting to
solid black in dark mode (it was bg-primary-foreground).
Previously the container and code blocks were both bg-primary-foreground,
which rendered the whole section black in dark mode with no contrast.
* perf(telemetry): batched off-pool writer for transactions + vertex_builds
Adds TelemetryWriterService that buffers transaction and vertex_build rows
in memory and drains them in batched INSERTs via a dedicated AsyncEngine
(pool_size=1 for SQLite, 2 for Postgres, max_overflow=0). Retention is
amortized in a 60s sweeper instead of running on every insert.
Producers (log_transaction, log_vertex_build) enqueue instead of opening
a DB session, so telemetry traffic no longer competes with the
request-handling pool. Falls back to the legacy direct-write path via
LANGFLOW_TELEMETRY_WRITER_ENABLED=false.
Durability: in-flight rows spill to a diskcache.Deque per PID on shutdown
and are restored on next startup; orphan PID directories left by crashed
workers are adopted.
* perf(telemetry): tighten error handling and add coverage
Review feedback from pr-review-toolkit + silent-failure-hunter:
- Retention sweep snapshots the dirty-flow sets before commit and only
clears them after it lands; a crashed sweep no longer drops the flows on
the floor, so per-flow caps cannot drift unboundedly.
- Producer fall-through is no longer silent. transaction_service and
log_vertex_build each log a one-shot WARNING when telemetry_writer_enabled
is True but the writer is not running.
- Lifespan startup failure now logs ERROR instead of WARNING — if the user
opted into the writer and it didn't come up, that's an error.
- Shutdown drain timeout no longer suppressed silently: logs a WARNING with
pending row count + a hint to raise telemetry_writer_shutdown_drain_s.
- Writer's flush loop catches asyncio.CancelledError separately and
re-prepends the in-flight batch to the buffer so teardown's disk spill
catches it.
- After 6 consecutive batch failures the writer emits a loud ERROR with
buffer depths so operators see sustained data-loss risk.
Added tests:
- test_sanitization_survives_writer_round_trip
- test_retention_failure_preserves_dirty_flows
- test_in_flight_batch_returned_on_cancel
* perf(telemetry): address copilot review
- _restore_from_disk + _adopt_orphan_outboxes now route through _enqueue so a
large disk-spilled or orphan outbox can't bypass telemetry_writer_max_queue
and OOM the process. Oldest rows are dropped and counted via the existing
dropped_transactions / dropped_vertex_builds counters.
- chmod 0o700 the outbox root + per-PID directory so sanitized-but-still-
sensitive payloads aren't exposed cross-user on multi-tenant hosts.
Suppressed on platforms where chmod is a no-op (Windows).
- Added test_adopt_orphan_outboxes_honors_max_queue.
The remaining two copilot notes (private API access to DatabaseService and
diskcache.Cache.close) were also flagged by the in-tree review; tracking
separately. The "diskcache not declared" note is a false positive — the
dependency is at src/backend/base/pyproject.toml:76.
* perf(telemetry): address coderabbit review
- Sweeper hands off dirty sets via capture-and-clear so concurrent
flushes during a retention pass aren't wiped by the post-commit
subtract; failure path restores the snapshot.
- Stress README uses a concrete Postgres DSN matching the docker
example instead of an unset env var.
- Test PID-probe loops bounded via a shared helper with pytest.fail
fallback.
* perf(telemetry): swap diskcache outbox for stdlib sqlite (CVE-2025-69872)
Replaces the diskcache.Deque-backed spill outbox with a stdlib sqlite3
outbox (WAL mode, JSON payloads in TEXT). diskcache 5.6.3 has
CVE-2025-69872 (pickle deserialization RCE for an attacker with write
access to the cache dir), no fixed version released, and was never
declared as a dependency — import failed on Python 3.10.
The replacement is encapsulated in a small _Outbox helper that owns the
connection, schema, JSON codec, and lifecycle. The codec uses tagged
wrappers for datetime and UUID so SQLAlchemy's typed columns accept
restored payloads on the way back out.
Hardening informed by a survey of OTel collector, Fluent Bit, Vector,
Prometheus remote_write, Datadog agent, Logstash, and Filebeat:
- Per-PID outbox dirs now stamp an owner.json (hostname + Linux boot_id
or time()-monotonic() proxy). Adoption only proceeds when host+boot
match; cross-host or pre-owner-file dirs are logged and skipped so a
recycled PID after a container restart cannot pull in a stranger's
spill data.
- PRAGMA synchronous=FULL on the spill connection so the shutdown
commit hits the platter (NORMAL only fsyncs on WAL checkpoint, which
may never run if the process exits immediately after commit).
- Spill honors telemetry_writer_max_queue with drop-oldest, matching
the producer-side overflow policy so a backlogged buffer at shutdown
can neither stall teardown nor fill disk.
- append_all encodes payloads up front and only clears the deque after
the transaction commits — no more partial-drain on mid-flight SQLite
failure. drain collapses to a single DELETE FROM outbox after the
SELECT.
- Exception handlers at the spill/restore/adopt boundaries narrowed to
(sqlite3.Error, OSError) so genuinely unexpected exceptions
propagate rather than being silently logged.
Tests cover: cross-host orphan skipped, missing-owner orphan skipped,
spill cap drops oldest, and a realistic UUID+datetime payload
surviving spill → restore → SQLAlchemy INSERT.
* [autofix.ci] apply automated fixes
* fix(telemetry): name wait_for inner tasks so pyleak can filter
Integration tests using @pyleak_marker were flagging an inner asyncio
task created by the telemetry writer's `wait_for(Event.wait(), ...)`
loops. The wrapper task is auto-named (Task-N) and lands mid-await
across the test boundary, so pyleak counts it as leaked. The writer
itself shuts down cleanly via teardown; the "leak" is a pattern
mismatch with pyleak's per-test snapshot model, not a real lifecycle
bug.
Wrap Event.wait() in an explicitly named task (`telemetry-writer-tick`,
`telemetry-writer-backoff`, `telemetry-sweeper-tick`) via a small
_wait_or_shutdown helper, and extend pyleak_marker with a task_name_filter
that excludes `telemetry-*` from leak detection. CI was green on earlier
commits in this branch only because the diskcache import error
prevented the writer from starting at all — fixing the import surfaced
this latent pyleak collision.
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* test(telemetry): disable writer in tests so reads see writes synchronously
* perf(telemetry): age out cross-host orphan outboxes on shared volumes
A pod on a shared PV (NFS/RWX) cannot adopt rows from a dead pod on a
different host because the dead pod's hostname doesn't match. Without a
janitor those directories accumulate forever. Add an owner-file mtime
heartbeat from the sweeper plus a prune pass that deletes cross-host
outboxes whose owner file hasn't been touched within
telemetry_writer_orphan_max_age_s (default 1h). Same-host orphans still
flow through the existing adoption path.
* perf(telemetry): add byte-aware flush + drop strategy
Today the writer bounds memory by row count only, so a worker logging fat
vertex_build artifacts can hold tens of MB per row and silently dwarf the
configured max_queue. Add a size_strategy switch ('count' | 'bytes' |
'either', default 'count' for parity) with two byte thresholds:
- batch_size_bytes (256KB) caps per-flush INSERT size when bytes apply
- max_queue_bytes (200MB) drops oldest by bytes when bytes apply
Parallel sizes deques mirror the payload buffers so accounting stays
consistent across drain, spill, restore, and the cancel/retry rollback.
Single rows above the byte budget are still emitted (no row is refused);
operators get dropped_*_bytes counters alongside dropped_*.
* test(telemetry): tighten byte-strategy assertions and cover end-to-end paths
Address gaps in the byte-aware strategy tests:
- pin exact dropped counts and remaining buffer state for the drop-by-bytes
case (was 'greater than zero')
- compute exact drain count from encoded size (was a 1-4 range)
- round-trip bytes through spill+restore to verify size deque is rebuilt
- round-trip bytes through orphan adoption for the same reason
- exercise the sweeper loop end-to-end to confirm heartbeat + prune are
wired in (previously only unit-tested in isolation)
Clarify in the settings docs that the byte caps measure encoded JSON size,
not Python in-memory footprint.
* ref: updates to telemetry writer PR (#13294)
* fix(telemetry-writer): harden error handling and add missing test coverage
Critical:
- teardown() now re-raises CancelledError after awaiting the writer task so the
asyncio cancellation chain propagates correctly on lifespan task kill
- suppress(OSError) on owner file write replaced with explicit error log so
operators know when disk-spilled rows will not be recoverable on restart
High / important:
- Escalation threshold check changed from == to >= so the error log fires on
every failure past the threshold, not just the 6th
- Dead `except OSError` on time.time() - time.monotonic() changed to
`except Exception` since time functions cannot raise OSError
- Removed redundant `from uuid import UUID` inside _run_retention_pass (already
imported at module top)
- Orphan directory cleanup: replaced blanket suppress(OSError) with per-child
suppress so ENOTEMPTY on an individual rmdir doesn't abort the whole loop and
leave the parent directory leaking silently; outer failure now logs at debug
Tests (3 new):
- test_retention_sweep_caps_vertex_builds_per_vertex: inserts 8 builds for a
single vertex with max_per_vertex=3 so the per-vertex DELETE subquery
actually executes (previous test used 8 distinct vertex IDs, bypassing it)
- test_either_strategy_trips_on_bytes_first: verifies bytes can be the first
trigger under 'either' strategy (previous test only covered the count-first path)
- test_writer_retries_on_batch_failure: injects 2 flush failures then success;
confirms failed_batches increments, rows are preserved in the buffer, and
flushed_rows reflects the final successful write
* ci: add stress-tests job to nightly build pipeline
Wires the stress-tests workflow into nightly so telemetry write stress
tests run automatically. Also gates release-nightly-build and
slack-notification on stress-tests result so a stress regression blocks
the nightly release and surfaces in Slack.
* ci: run stress tests in nightly without blocking release
Stress tests are informational for now — a failure is visible in the
workflow run and Slack but does not gate the nightly release or build.
* fix(telemetry-writer): address PR review on cancelled teardown and nightly stress tests
- Add the stress-tests.yml reusable workflow (was untracked, so the
nightly stress-tests job could never resolve)
- Notify Slack on stress-tests failure (add to slack-notification gate
and FAILED_JOB detection as non-blocking)
- Run sweeper cancel, disk spill, and engine dispose in a finally so a
cancelled teardown() still persists the in-memory buffer
- Add tests for the cancelled-teardown spill path and the >= escalation
threshold; drop the misleading wait-loop in the retry test
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jordan Frazier <122494242+jordanrfrazier@users.noreply.github.com>
* fix: commit slider value before node selection consumes the interaction
Sliders inside React Flow nodes (e.g. the URL component's Depth field) lost
the value the user set when the node was not yet selected. React Flow selects
a node on click and pans/drags it on pointer down, while Radix drives the
slider with the same pointer events. The interactive SliderPrimitive.Root had
no React Flow isolation, so the first interaction on an unselected node was
consumed by node selection: the slider reacted visually but the chosen value
reverted or snapped to wherever the pointer landed.
Stop pointer/click propagation on the slider root and add React Flow's
nodrag/nopan/noflow/nowheel opt-out classes (matching the slider's value-text
input). Radix composes the handlers, so value setting is unaffected.
Adds a regression test asserting slider pointer-down/click do not bubble to
the node wrapper while unrelated children still do.
* Update src/frontend/src/components/core/parameterRenderComponent/components/sliderComponent/__tests__/slider-node-selection.test.tsx
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* [autofix.ci] apply automated fixes
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix(i18n): translate missing frontend strings found in QA
- API Keys page: replace hardcoded "Never" with t("settings.apiKeys.never")
- New Flow screen: migrate all WELCOME_* string constants in
flow-builder-welcome.tsx to t() calls; remove string constants from
flow-builder-welcome.constants.ts (keep WELCOME_MAX_INPUT_LENGTH)
- Get Started sidebar card: add useTranslation and replace 5 hardcoded
strings (All Set, Get started, Star repo, Join the community, Create a
flow) with existing sidebar.* i18n keys
- en.json: add settings.apiKeys.never + 10 flowBuilderWelcome.* keys
- All non-English locale files (ja, fr, es, de, pt, zh-Hans) updated via
GP upload/download
* fix(i18n): translate additional QA-identified untranslated strings
- Translate canvas assistant banner ("Try the new Langflow Assistant!", "New" pill)
- Translate search placeholder flow type name (was showing raw "flows"/"mcp" in mixed-language)
- Translate UNNAMED tool badge in ToolsComponent and ToolsTable
- Extract all 61 flow default descriptions to i18n keys (flow.defaultDescription.0-60)
so new flows get a localized random description instead of always English
- Fix backend ja.json: "Vector Store RAG" → "ベクターストア RAG" for consistency
with the frontend welcome screen translation
- Run GP upload+download to populate fr, ja, es, de, pt, zh-Hans locales
* [autofix.ci] apply automated fixes
* fix(i18n): fix CI failures — Biome import order and updated test constants
- Fix Biome import sort in flow-builder-welcome.tsx (lucide-react after react)
- Update flow-builder-welcome test to use inline English strings instead of
importing the deleted WELCOME_* constants from flow-builder-welcome.constants
* fix(i18n): truncate long checklist labels with tooltip in get-started sidebar
Long translations (e.g. Japanese) were wrapping to two lines in the narrow
sidebar. Now each label truncates with ellipsis and shows the full text in a
tooltip on hover. Icons also get shrink-0 to stay fixed-size.
* [autofix.ci] apply automated fixes
* fix(i18n): align assistant banner translation with panel title in ja, es, pt
GP inconsistently kept "Assistant" in English in the banner string while
translating it natively in the panel title. Patched directly:
- ja: "Assistant" → "アシスタント"
- es: "Assistant" → "Asistente"
- pt: "Assistant" → "Assistente"
fr, de, zh-Hans were already consistent.
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix: prevent X-Forwarded-For bypass of login rate limit
* fix: update drift-guard test to include forwarded_allow_ips key
---------
Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local>
The LFX 0.5.0 -> 1.10.0 realignment (#13176) left every src/bundles/*
package and the port_bundle.py generator flooring lfx>=0.5.0. That
silently permits resolving against the now-dead 0.5.x line: a bundle
built against 1.10.0's BUNDLE_API would ImportError there, and the
BUNDLE_API_VERSION check (both "1") would not catch it. RELEASE.md flags
exactly this — the jump "affects downstream pins, and neither pip nor uv
will flag it."
- Bump the 4 bundle pyprojects (arxiv, docling, duckduckgo, ibm) to
"lfx>=1.10.0,<2.0.0": floored at the current major.minor line, capped
below the next lfx major. Fine-grained API compat stays enforced via
extension.json's lfx.compat against BUNDLE_API_VERSION, not the cap.
- Add scripts/ci/sync_bundle_lfx_pin.py and call it from `make patch` so
future releases keep bundle floors in step. Idempotent: a no-op on
patch releases, moves the floor on minor/major bumps. Leaves docling
self-refs and the nightly lfx-nightly== form untouched.
- port_bundle.py now derives the floor from src/lfx/pyproject.toml, so
newly-ported bundles are born at the current line.
- Update src/bundles/PORTING.md and add test_bundle_lfx_pin.py (20 tests).
* feat(lfx): synchronize LFX onto Langflow major.minor version line (Phase 1)
- Bump src/lfx/pyproject.toml from 0.5.0 to 1.10.0
- Tighten langflow-base lfx pin: ~=0.5.0 → ~=1.10.0
- Fix release.yml pre-release boundary from next-major to next-minor (<X.(Y+1).dev0)
- Add minor-parity soft check to scripts/release-lfx.sh
- Extend make patch to sync lfx version alongside langflow/base/frontend
- Document LFX compatibility contract in RELEASE.md
Contract: LFX X.Y.N is compatible with any Flow from Langflow X.Y.M.
* feat(lfx/upgrade): add compatibility checker (Phase 2)
* fix(lfx/upgrade): fix registry_code guard and types order-sensitivity in checker
* feat(lfx/upgrade): implement safe-upgrade applier (Phase 2)
* feat(lfx): add lfx upgrade command (Phase 2)
* feat(lfx/run): add --upgrade-flow option (Phase 2)
* feat(lfx/serve): add --upgrade-flow option (Phase 2)
* update lfx pyproject version
* [autofix.ci] apply automated fixes
* test(lfx/upgrade): add v1.9.0 starter flow fixtures for upgrade integration tests
* test(lfx/upgrade): add v1.9.0 starter flow fixtures and real-flow integration tests
* [autofix.ci] apply automated fixes
* fix(lfx/upgrade): use alias-aware registry lookup so renamed components are not falsely blocked
* fix(lfx/upgrade): handle outer flow envelope and file-path inputs in upgrade checks
* fix(lfx/upgrade): fail-fast on upgrade_flow errors; add regression tests for Bugs 1-3
* chore(tests): remove bug-number labels from test comments
* [autofix.ci] apply automated fixes
* fix(lfx/upgrade): address PR review comments
- Reject .py files early in serve --upgrade-flow with a clear error message
- Preserve outer flow envelope metadata (name, description, etc.) when lfx upgrade --write rewrites a file
- Move Mapping import under TYPE_CHECKING to satisfy Ruff TC003
- Assert fixture flows and registry are non-empty so parametrized tests cannot pass vacuously
- Remove unused capsys arg and replace print with sys.stderr.write (Ruff T201/ARG001)
- Add upgrade_flow param to run command docstring
* fix(lfx/upgrade): preserve envelope on run, apply nested upgrades (#13200)
* fix(lfx/upgrade): preserve envelope on run, apply nested upgrades
Three follow-ups on top of the upgrade tooling:
- run --upgrade-flow: re-attach the inner graph to the outer envelope before handing the flow to aload_flow_from_json. Previously the upgrade path unwrapped {"data": ...} and passed the inner dict to the loader, which raised KeyError: 'data'. Adds happy-path tests for envelope and flat file inputs.
- applier: recurse into one level of nested flow nodes (node.data.node.flow.data.nodes), matching the checker. Without this, outdated_safe nodes inside grouped components were reported but never written. Adds a regression test.
- upgrade --write test: assert the envelope is preserved (name, description, endpoint_name, etc.) instead of the previous unwrapped-output expectation, so the test matches the actual fix in this PR.
- Drive-by ruff cleanup: D417 docstring, SIM103, SIM108, E501, RUF059.
* [autofix.ci] apply automated fixes
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix(lfx/upgrade): fix loader KeyError, outer-envelope checker gap, and nested-node applier
Three bugs from ogabrielluiz's PR review:
1. run/base.py loader KeyError: file-path branch unwrapped the outer envelope
for the checker but passed the inner dict to aload_flow_from_json, which does
flow_graph["data"] unconditionally. Re-wrap as {"data": flow_dict} before the
loader call. Applied consistently to all three input paths (--flow-json,
--stdin, file-path).
2. run/base.py zero-node checker for --flow-json/--stdin: those paths stored the
raw parsed JSON without unwrapping, so a caller passing an exported flow
{"name":..., "data":{...}} caused the checker to see zero nodes and silently
pass. Now unwrap with raw.get("data", raw) matching the file-path branch.
3. applier.py nested flows never upgraded: the early `continue` on top-level
nodes not in safe_ids prevented the nested-node loop from running. Restructured
so the nested check fires unconditionally for every top-level node, mirroring
checker.py:160-165.
* fix(lfx/run): unify outer-envelope unwrap across all --upgrade-flow input paths
File-path reads now call raw.get("data", raw) matching --flow-json and
--stdin, so the upgrade checker always sees the inner graph. This also
removes the has_envelope/re-wrap machinery that was double-wrapping
outer-envelope files when passing to aload_flow_from_json.
Fix two test assertions that described the old double-wrapped shape.
* fix(lfx/upgrade): inject registry into upgrade_command; fix Makefile lfx pin regex
- upgrade_command now accepts an optional registry parameter so tests
can pass the dict directly instead of mocking load_registry_from_index
- Makefile sed regex broadened from \"lfx~=.*\" to match both ~= and >=
forms so make patch works after release.yml rewrites the pin
- Echo label changed to LFX (synced) to clarify the variable is the
shared Langflow version, not a separate LFX-specific value
* fix(make/patch): fix langflow-base sed regex and validation grep
The dependency in pyproject.toml is "langflow-base[complete]>=X.Y.Z",
not the "langflow-base==X" form the original regex expected, so make
patch silently left the pin unchanged and the validation step always
failed. The sed pattern now matches any extras/operator combination and
rewrites to the canonical [complete]>= form; the grep uses -F so the
[complete] brackets are treated literally.
* test(lfx): add patch regex tests and symmetric safe-mode envelope tests
test_patch_regexes.py — 15 tests covering the Python regexes embedded in
the Makefile patch target. Exercises all three substitutions (langflow-base
pin, lfx pin, version field) against every realistic pin format including
the >=X.Y.Z,<dev0 form that release.yml writes. Would have caught the
langflow-base==.* vs [complete]>= mismatch before manual testing.
test_base.py — two new TestUpgradeFlowOption tests:
test_upgrade_flow_safe_envelope_inline_json_loads_successfully
test_upgrade_flow_safe_envelope_stdin_loads_successfully
Symmetric to the existing file-path envelope test; verifies that --flow-json
and --stdin with an outer-envelope flow also pass {"data": inner} to the
loader after safe upgrades, not a double-wrapped {"data": outer_envelope}.
* fix(lfx/upgrade): address review: compat checker, shared gate, fail-fast registry
Checker correctness:
- _outputs_are_compatible: drop cosmetic display_name from the breaking check;
treat widened output types as safe (flow types must be a subset of registry
types), only narrowing breaks downstream edges.
- _input_types_contained: stop flagging widened input_types as breaking; keep
narrowing as the only breaking case; fix misleading comments.
- check_flow_compatibility now recurses fully into nested grouped components
(symmetric with the applier) and accepts a pre-built registry lookup.
CLI/run/serve:
- New lfx.upgrade.cli_gate (UpgradeFlowMode enum, UpgradeFlowError,
apply_upgrade_gate) shared by run_flow and serve_command so the two
--upgrade-flow paths can't diverge.
- run_flow: extract _materialize_flow_dict and route gating through the shared
helper.
- run/serve --upgrade-flow options typed as UpgradeFlowMode (check|safe choices).
- lfx upgrade: load_registry_from_index fails fast when the bundled registry is
empty/missing instead of silently marking every node blocked; ASCII-only report
output; new --strict flag; build the registry lookup once and reuse it.
Docs/tests:
- RELEASE.md: migration note for the lfx 0.5.0 -> 1.10.0 version jump.
- Regression tests for the checker fixes, the shared gate, fail-fast registry,
--strict, and serve --upgrade-flow parity.
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* fix(lfx): consolidate flow-envelope handling and fix version-ceiling regex
Extract the outer-envelope unwrap/rewrap logic (previously hand-rolled as
raw.get("data", raw) with subtly different rules across serve, run, and
upgrade) into a single lfx.utils.flow_envelope module with split/merge
helpers. This fixes a serve bug where an enveloped flow had its inner graph
written bare to the temp file, making the loader's flow_graph["data"] raise
KeyError.
Also fix release.yml's major.minor extraction: the greedy sed grabbed the
upper bound from a range pin (>=1.10.0,<1.11.dev0), drifting the version
ceiling up one minor each release cycle. Anchor to the first version instead.
* fix(lfx): upgrade-flow gate reads bundled component index, not empty cache
The --upgrade-flow=check|safe gate on lfx run and lfx serve rejected every
flow as 'blocked'. Both call sites passed component_cache.all_types_dict to
apply_upgrade_gate, but that cache is populated lazily after services start,
so at gate time it is empty -- an empty registry classifies every node as
blocked. The standalone lfx upgrade command was unaffected because it reads
the bundled _assets/component_index.json instead.
Make the gate own registry loading: apply_upgrade_gate now defaults
all_types_dict to None and loads the bundled index (the same source lfx
upgrade uses) via a new _load_bundled_registry helper, raising
UpgradeFlowError on a missing/empty index so a broken install fails loudly
instead of silently blocking every component. Both call sites pass mode=
and let the gate load the registry.
Existing gate tests mocked component_cache with a populated registry, which
is exactly what hid the bug; repoint them at the new _load_bundled_registry
seam and add regression tests that do not mock the registry, including an
end-to-end run_flow check against a real clean v1.9.0 starter flow.
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@logspace.ai>
DB2VectorStoreComponent.search_documents() returned early when no search query was provided, before calling build_vector_store() — which is what ingests ingest_data. As a result, ingestion only ran when a search query was also supplied.
Build (and ingest into) the vector store first, then skip only the search when no query is present, matching LCVectorStoreComponent. Adds a regression test asserting ingestion runs with no query.
* fix: enable agent-lifecycle-toolkit (altk) on Python 3.14
agent-lifecycle-toolkit 0.10.x dropped its own requires-python <3.14
cap (now >=3.10) and does not depend on OpenDsStar, so the
`python_version < '3.14'` gate on the altk extra is stale.
- Remove the python_version < '3.14' marker from the altk extra in
langflow-base, keeping the macOS x86_64 platform exclusion.
- Regenerate uv.lock: agent-lifecycle-toolkit resolves on 3.14 and
coexists with the onnxruntime>=1.26 langflow forces there
(verified agent-lifecycle-toolkit 0.10.1 + onnxruntime 1.26.0).
OpenDsStar (the separate OpenDsStarAgent component) stays gated: all
its releases still cap requires-python at <3.14 upstream.
* test: correct stale <3.14 comment in ALTK test import guards
The module-level import skips in the ALTK agent tests carried an
outdated comment claiming "agent-lifecycle-toolkit is gated to
python_version<'3.14' upstream". That is no longer accurate:
agent-lifecycle-toolkit 0.10.1 dropped the <3.14 cap (now requires
>=3.10), and this PR removes the langflow <3.14 marker on the altk extra.
The skip itself is preserved -- altk is an optional extra
(langflow-base[altk]) that may be absent from a given environment, so
the ImportError guard is still required. Only the comment is corrected,
to state the real reason for the skip plus the upstream change. Applied
to all four files carrying the identical comment (test_altk_agent,
test_altk_agent_logic, test_altk_agent_tool_conversion,
test_conversation_context_ordering).
Also tag a pre-existing fake test key (api_key="sk-test") with
`# pragma: allowlist secret`; the comment shift re-staged the file and
surfaced it to detect-secrets.
Validated (Python 3.13, altk installed): ruff clean; the three logic
modules run (64 passed, 4 skipped) and test_altk_agent collects (13).
* fix: enable IBM watsonx.ai bundle on Python 3.14
ibm-watsonx-ai (1.5.13) and langchain-ibm (1.1.0) added Python 3.14
support upstream on 2026-06-03, so the official 3.14 Docker image no
longer needs to silently drop the IBM integration.
- Lift the `python_version < '3.14'` markers on ibm-watsonx-ai and
langchain-ibm in both the lfx-ibm bundle and langflow-base.
- Bump langchain-ibm from `~=1.0.2` to `~=1.1.0`; the 3.14-capable
release is 1.1.0, which the old `~=1.0.2` pin excluded.
- Regenerate uv.lock: the resolver now forks ibm-watsonx-ai into
1.5.13 for py>=3.11 (incl. 3.14) and 1.3.42 for py<3.11, so 3.10
keeps working while 3.14 gains the integration.
watsonx-orchestrate (ibm-watsonx-orchestrate-core/clients) stays gated
at <3.14 since upstream 2.10.0 still caps there.
* test: stop import-skipping IBM watsonx tests on Python 3.14
The watsonx test modules skipped at module load (pytest.skip with
allow_module_level=True) when langchain-ibm / ibm-watsonx-ai failed to
import, with comments tying the skip to the upstream <3.14 cap. Now that
those deps are importable on 3.14 (this PR's dep bump), the import-based
skip can hide real import regressions instead of surfacing them.
- test_model_utils.py, test_watsonx.py, test_watsonx_embeddings.py:
replace the try/except pytest.skip(allow_module_level=True) guards with
direct top-level imports so a missing or broken IBM SDK fails loudly.
Drop the now-unused `import pytest` in test_model_utils.py.
- src/bundles/ibm/README.md: update the platform note to reflect that the
watsonx components are now importable on Python 3.10-3.14.
The Db2 tests (test_db2*, test_optional_dependency) are left untouched --
they gate on ibm-db's linux/aarch64 platform exclusion, not the Python
version, which is still valid.
Validated locally (Python 3.13): 63 tests pass (13 + 50), ruff clean.
* feat(security): add failed login logging with IP tracking and comprehensive tests
* fix: remove PII from login logs and fix structlog format
* docs: agent improvements (#13269)
* docs: add structured output for agents
* docs: add structured output note
* docs: clarify both agent outputs and add release notes
* docs: when langflow sends events to the playground and chat history
* fix(playground): expand session checkbox click target to full row height (#13349)
* fix(playground): expand session checkbox click target to full row height
The selectable-row checkbox wrapper was ``w-4 h-4`` (16x16 px) but the
row is ``h-8`` (32 px). The 8 px band above and the 8 px band below
the visible icon were dead zones that bubbled to the row's
``toggleVisibility`` and navigated to the session instead of toggling
selection — exactly the accidental-navigation UX the bug ticket
describes.
Grow the wrapper to ``w-4 h-8`` so the click target captures the full
row height. Width stays narrow so the text alignment is unchanged; the
icon itself stays ``h-4 w-4``, centered via the existing
``flex items-center justify-center``. Visual position is identical.
Adds ``__tests__/session-selector-checkbox-click-target.test.tsx``
(3 cases): wrapper className guard (h-8 + w-4, not h-4), click on the
wrapper outside the icon triggers selection without bubbling to
toggleVisibility, and click on the inner icon still toggles selection.
* test(playground): drop checkbox-click-target test file (not needed per review)
* [autofix.ci] apply automated fixes
* test: align session-selector checkbox assertions with full-height click target
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix: improve trace search functionality (#13383)
* improve trace search functionality
* [autofix.ci] apply automated fixes
---------
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* test: fix test_login_logs_real_output_format to use mocking
- Updated test to use patch() instead of capsys for consistency
- Verifies structlog kwargs format (not nested extra dict)
- All 7 login logging tests now passing
* feat: Add IP-based rate limiting to login endpoint
- Add slowapi dependency for rate limiting functionality
- Implement rate limiting service with configurable limits
- Apply rate limiter to /login endpoint (default: 5 attempts/minute per IP)
- Add custom exception handler for rate limit exceeded (HTTP 429)
- Support both in-memory (default) and Redis storage backends
- Add comprehensive test suite with 100% coverage
- Document configuration in .env.example
Security improvements:
- Prevents brute force attacks on login endpoint
- Structured logging without PII (logs client_ip, not user data)
- Graceful degradation with swallow_errors=True
- Proper X-Forwarded-For header handling for proxied requests
Configuration:
- LANGFLOW_RATE_LIMIT_PER_MINUTE: requests per minute (default: 5)
- LANGFLOW_RATE_LIMIT_STORAGE: storage backend (default: memory://)
- LANGFLOW_RATE_LIMIT_HEADERS_ENABLED: enable rate limit headers (default: false)
* security: add proxy-aware IP extraction for rate limiting
- Add LANGFLOW_RATE_LIMIT_TRUST_PROXY configuration
- Default to secure get_remote_address (prevents header spoofing)
- Enable get_client_ip when behind trusted proxies
- Add comprehensive tests for both configurations
- Maintain 100% test coverage (15 tests passing)
* refactor(rate-limit): address PR review feedback - migrate to Settings model, fix IP security, add test coverage
* fix: disable rate limiting in tests to prevent 429 errors
- Add LANGFLOW_RATE_LIMIT_ENABLED environment variable control
- Disable rate limiting by default in tests via session-scoped fixture
- Rate limit tests explicitly enable it via function-scoped fixture
- Fixes test failures caused by sequential login attempts hitting 5/min limit
* fix: refactor login rate limiting to avoid settings initialization race condition
---------
Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local>
Co-authored-by: Mendon Kissling <59585235+mendonk@users.noreply.github.com>
Co-authored-by: keval shah <kevalvirat@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: olayinkaadelakun <olayinka.adelakun@ibm.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
* chore: update lock files
update lock files
* fix: migrate Mongo/Weaviate/Perplexity components off removed langchain-community classes
langchain-community 0.4.2 (pulled in by the lock update) removed the
MongoDBAtlasVectorSearch and Weaviate vector stores and the ChatPerplexity
chat model. The MongoDB import failed at test collection, aborting both
backend unit-test groups with an ImportError.
Migrate to the standalone packages, all already declared dependencies:
- mongodb: langchain_mongodb.MongoDBAtlasVectorSearch (drop-in)
- perplexity: langchain_perplexity.ChatPerplexity (drop-in)
- weaviate: rewrite for weaviate-client v4 (connect_to_weaviate_cloud /
connect_to_custom) + langchain_weaviate.WeaviateVectorStore, adding
advanced gRPC host/port inputs. The component was already broken on
weaviate-client v4, which removed the v3 weaviate.Client(url=...) API.
Component class names and identifiers are unchanged, so existing flows are
preserved. The component index is left for the autofix CI job to regenerate.
* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]
* ci: wake up CI
Co-Authored-By: Oz <oz-agent@warp.dev>
* test: fix concurrent-import deadlock in test_all_modules_importable
test_all_lfx_component_modules_directly_importable imports ~488 modules
concurrently via asyncio.gather over asyncio.to_thread(importlib.import_module).
It flaked ~50% with:
_DeadlockError: deadlock detected by _ModuleLock('toolguard.runtime.runtime')
on lfx.components.models_and_agents.policies.tool_invoker
toolguard has an internal circular import: toolguard/runtime/__init__.py does
`from .runtime import ...` while runtime.py does `from toolguard.runtime import
IToolInvoker`. It resolves fine single-threaded, but the lfx policy modules reach
the cycle from two entry points at once -- policies.tool_invoker enters at the
toolguard.runtime package while policies.guard_sync_utils enters at the
toolguard.runtime.runtime submodule. On separate worker threads one holds the
package lock waiting on the submodule lock while the other does the reverse, so
CPython's import machinery raises _DeadlockError.
Pre-import both entry points single-threaded before the fan-out so sys.modules is
warm and the threaded imports only hit the cache. Keeps full parallelism and
coverage (every module is still imported; no skip-list entry).
Verified 24/24 green vs a 4/8 baseline.
* add tests
* pragma
* Update component_index.json
* [autofix.ci] apply automated fixes
* test: fix dropdownComponent fixture import for langchain-community 0.4.2
The dropdownComponent Playwright test pastes component code into the code editor and clicks "Check & Save", which validates the code by importing it. langchain-community 0.4.2 (this branch's lock bump) removed `langchain_community.chat_models.bedrock`, so the import threw, the code modal stayed open, and enableInspectPanel timed out clicking `canvas_controls_dropdown_help` through the open dialog. Failed deterministically (all retries + GHA re-run), only on this branch.
Swap the fixture to `langchain_aws.ChatBedrock`, matching the real Amazon Bedrock component which already migrated, and upstream guidance (BedrockChat deprecated since lc 0.0.34).
Also move the 0.4.2-removed `langchain_community.chat_models.litellm` import off the top level of the deactivated ChatLiteLLM component into build_model so the module imports cleanly. No loaded/product components affected.
* [autofix.ci] apply automated fixes
* chore: update pandas and numexpr
* Update component_index.json
* chore: update templates
---------
Co-authored-by: Eric Hare <ericrhare@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Oz <oz-agent@warp.dev>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix(conditional-router): route Text Input when Case True/False are blank
The If-Else component's Case True and Case False fields are optional
overrides. When left blank, the MessageInput resolved them to an empty
Message, so the matched branch emitted a blank message instead of the
original Text Input.
Fall back to input_text when the override message is empty, so the
incoming message is passed through to the True/False branch as expected.
Fixes#13387
* fix(conditional-router): use Message(text=) for empty stopped-branch output
The non-matched (stopped/excluded) branches returned Message(content=""),
but 'content' is not a Message field — it was stored as an arbitrary data
key, leaving .text empty. Use Message(text="") so the empty output is a
proper empty Message.
* [autofix.ci] apply automated fixes
* fix(conditional-router): preserve non-text Message overrides
A Case True/False override can be a real Message with empty .text but a
meaningful payload (files or content blocks). The fallback helper treated
any Message with falsy .text as blank and replaced it with the input text,
dropping that payload.
Only fall back to input_text when the override carries no payload at all
(no text, no files, no content_blocks); otherwise preserve the override
as-is.
* fix: address review comments
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix: enforce mutually exclusive component constraint on paste
Pasting a component (e.g. Chat Input) no longer bypasses the
mutual-exclusivity rule when a conflicting component (e.g. Webhook)
already exists in the flow. The paste flow previously only checked the
singleton constraint, so a copy/paste could place both Chat Input and
Webhook in the same flow and break flow logic.
- Add MUTUALLY_EXCLUSIVE_COMPONENTS as the shared source of truth in
constants; the sidebar exclusivity rules now alias it
- Add filterMutuallyExclusiveComponents helper, wired into flowStore
paste; fast-paths non-constrained pastes and guards against
prototype-key clipboard input
- Internationalize the paste notice across all 7 locales
- Add unit tests covering existing-flow conflicts, intra-selection
conflicts, edge cleanup, and adversarial input
* refactor: unify component placement policy into one constraint module
Address review: placement policy was duplicated across five locations
(sidebar booleans, EXCLUSIVITY_RULES, checkChatInput/checkWebhookInput,
singleton paste filter, mutual-exclusivity paste filter), which is how
the paste-validation gap drifted in.
- Add utils/componentConstraints.ts as the single source of truth:
COMPONENT_CONSTRAINTS plus a pure rule engine (evaluatePlacement,
filterPlaceableSelection, getPresentComponentTypes)
- Sidebar disable/tooltip and flowStore paste both consume the engine;
remove the four duplicated policy encodings
- Make the paste filter side-effect free (returns { nodes, edges,
violations }); flowStore surfaces the notice and collapses singleton
and mutual-exclusivity into one pass
- Move policy out of the global constants dump into the focused module
- Internationalize both paste notices across all 7 locales
- Update tests for the new shapes; add componentConstraints unit tests
* test: type sidebarItemsList mocks to satisfy noExplicitAny
* docs: add design spec for lfx serve multi-flow startup and dynamic upload
* docs: add implementation plan for lfx serve multi-flow startup and dynamic upload
* feat(serve): add FlowRegistry for mutable in-process flow management
* feat(serve): add flow_id_from_content for content-based flow ID generation
* fix(test): update streaming tests to use FlowRegistry API
* test(serve): add upload endpoint tests
* feat(serve): add build_registry_from_directory and build_registry_from_paths helpers
* refactor(serve): remove dead _analyze_graph_structure and _generate_dynamic_run_description helpers
* feat(serve): accept directory and multiple file paths at startup; wire registry to serve_command
* [autofix.ci] apply automated fixes
* fix(serve): move traceback import to module level; remove planning docs from branch
* [autofix.ci] apply automated fixes
* fix(serve): deepcopy graph in stream_flow; use json.dumps for error stream payload
* test(serve): verify stream setup error payload is valid JSON with special characters
* [autofix.ci] apply automated fixes
* fix(serve): use load_flow_from_json directly in _load_graph_and_meta for clear error messages
* docs(serve): document multi-worker and persistence limitations on FlowRegistry
* feat(serve): allow starting server with no flows; accept full Langflow flow JSON on upload
- Remove the requirement for a flow path/JSON/stdin at startup; lfx serve
now starts with an empty FlowRegistry when no input is provided, ready
to accept flows via POST /flows/upload/
- UploadFlowRequest now accepts the full Langflow export JSON directly
(extra="allow", id field added) so callers can do -d @flow.json without
wrapping; load_flow_from_json receives the full dict and the flow's own
stable id is used when present
* fix(serve): harden upload endpoint and remove unused verbose_print param
- Pass body.model_dump(exclude={"replace"}) to load_flow_from_json instead of
body.model_dump(); keeps the top-level "data" key the function expects while
excluding the internal replace flag
- Set graph.flow_id after upload so it matches the registry key
- Return 409 Conflict on duplicate flow ID; add replace=true field to allow
explicit overwrite
- Remove unused verbose_print param from _load_graph_and_meta and update callers
- Clarify directory scan is top-level only (non-recursive) in CLI help text
- Add test_upload_full_export_as_body regression test: sends a complete Langflow
export JSON as the request body (curl -d @flow.json pattern) where body.data
has no nested "data" key — would have caught the KeyError('data') regression
* fix(serve): address PR review comments
- Restore .py script support: _load_graph_and_meta now dispatches to
load_graph_from_script for .py paths; CLI validation accepts .json and .py;
help text updated in both _running_commands.py and commands.py
- Fix flow ID collision in build_registry_from_paths: compute a shared common
root from all supplied paths so same-named files in different directories
get distinct UUIDv5 IDs instead of silently overwriting each other
- Harden FlowRegistry.add(): add overwrite=False parameter that raises
ValueError on duplicate IDs; startup paths keep default (collision = error),
upload endpoint passes overwrite=body.replace
- Add regression test: test_same_filename_in_different_dirs_gets_distinct_ids
- Update test_duplicate_add_replaces_graph to reflect new overwrite semantics
* test(serve): add unit tests for .py script path in serve command
- test_load_graph_and_meta_dispatches_to_script_loader_for_py: verifies
_load_graph_and_meta calls load_graph_from_script (not load_flow_from_json)
for .py paths, and that meta.title and relative_path are set correctly
- test_serve_command_accepts_py_file: end-to-end check that lfx serve my.py
reaches the server (exit_code == 0)
- test_serve_command_rejects_unsupported_extension: .txt and other unknown
extensions must exit non-zero with a helpful message
* [autofix.ci] apply automated fixes
* Add --no-env-fallback option to support scoped request_vars
* [autofix.ci] apply automated fixes
* feat(serve): filesystem-backed flow registry for multi-worker sharing
Adds a FlowStore abstraction with two implementations:
- NullFlowStore (default): in-memory only, existing behavior unchanged
- FilesystemFlowStore: persists each flow as {id}.json; writes are
atomic (tmp→rename); reads and deletes are TOCTOU-safe; path
traversal guard rejects unsafe flow IDs
FlowRegistry now delegates persistence to FlowStore:
- add(raw_json=...) writes to store when raw JSON is available
- get() falls through to store on cache miss, reconstructs + caches
- list_metas() merges in-memory and store-side entries
- remove() propagates deletion to store
- warm_from_store() pre-warms the in-memory cache at startup
New --flow-dir option on lfx serve activates the filesystem store.
All uvicorn workers pointing at the same directory serve the same
flows — use /tmp/lfx-flows for single-pod sharing or a PVC mount
for cross-pod sharing. Raw JSON is plumbed through _load_graph_and_meta
and both builder functions so startup-loaded .json flows are persisted
automatically; .py flows remain in-memory only.
* feat(serve): add --workers option for multi-worker uvicorn support
Adds --workers N to lfx serve, enabling multiple uvicorn processes to
share flows via --flow-dir.
serve_command is restructured from @syncify async to plain sync: all
async registry-building is extracted into _build_serve_registry() and
called via asyncio.run(), then uvicorn.run() handles the server.
For workers > 1, uvicorn requires an import string rather than an app
object. The factory create_serve_app() in serve_app.py reads
LFX_SERVE_FLOW_DIR and LFX_SERVE_NO_ENV_FALLBACK from env, pre-warms
from the shared FilesystemFlowStore, and returns a ready FastAPI app.
The parent sets those env vars unconditionally before dispatching.
For workers == 1, the pre-built app object is used directly (no change
to existing single-worker behavior). A warning is emitted when
--workers > 1 is used without --flow-dir since each worker would have
an isolated in-memory registry.
The startup panel flow count is now accurate for single-worker +
--flow-dir by calling warm_from_store() before printing.
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* [autofix.ci] apply automated fixes (attempt 3/3)
* fix(serve): remove no_args_is_help, drop verbose_print param, harden _safe_path
- Remove no_args_is_help=True from serve command so lfx serve with no
arguments starts with an empty registry instead of showing help
- Remove unused verbose_print parameter from create_multi_serve_app and
update all callers (14 call sites across tests)
- Wrap _safe_path's relative_to() check in try/except for a clear error
message; restructure delete() to use try/except/else (TRY300)
- Add TYPE_CHECKING guard for FlowStore annotation in commands.py; wrap
long --workers help string; restore FBT003 noqa on no_env_fallback
- Fix test_env_fallback_skipped_when_flag_true to allow logger's getenv
calls while still asserting the credential variable is never looked up
- Fix ARG002/D205/RUF059 lint issues in test files
* [autofix.ci] apply automated fixes
* fix(serve): resolve merge conflicts and clean up test section comments
* ruff - test updates
* fix(serve): cross-worker delete propagation and HTTP 500 for execution failures
Cross-worker stale cache (multi-worker DELETE bug):
- Add FlowStore.is_persistent to distinguish real stores from NullFlowStore
- Track _store_sourced in FlowRegistry for flows written to a persistent store
- get() re-reads the store for store-sourced flows to detect cross-worker deletes;
evicts and returns None if the file is gone
- list_metas() skips store-sourced flows whose store key is no longer present
- Add _evict() helper to clean up _flows, _store_meta_cache, _store_keys, _store_sourced
HTTP status for execution failures:
- POST /flows/{id}/run now returns HTTP 500 (not 200) when execution raises or
when the flow returns success=false; body is still RunResponse for debug detail
- Updated OpenAPI responses declaration: 500 now maps to RunResponse model
* fix(serve): harden multi-worker registry correctness and safety
- Add FlowAlreadyRegisteredError (ValueError subclass) so upload_flow
catches only the specific race-condition exception rather than any
ValueError from unrelated internals.
- Add _SERVE_ENV_PREFIX constant; rebuild LFX_SERVE_* env var names from
it and add try/finally cleanup so those vars are always deleted after
uvicorn.run() returns in multi-worker mode.
- Reject .py startup files when --workers > 1: Python graphs cannot be
serialized to the filesystem store so workers would silently start with
empty registries for those flows.
- Remove warm_from_store() from build_registry_from_paths/directory; the
builders no longer pre-warm to avoid double-loading when the startup dir
and flow-dir overlap. serve_command still warms for single-worker mode.
- Fix FlowRegistry.__len__ to delegate to list_metas() so store-only
flows (not yet cache-loaded) are counted; this makes the startup panel
and /health flow_count accurate for multi-worker + pre-placed files.
- Fix FlowRegistry.remove() to delete both the primary store key file and
any alternate-keyed file (UUID or stem), ensuring cross-worker DELETE
propagation: after a delete, every worker's next request to that flow
triggers the per-request stale check, reads None from the store, and
returns 404 regardless of which key its cache entry uses.
- Document the per-request stale check in FlowRegistry docstring with
latency guidance for network volumes vs. local tmpfs.
- Add tests for all of the above, including a cross-worker DELETE
propagation scenario validated by a live 2-worker integration test.
* fix(serve): load startup flows in every uvicorn worker via LFX_SERVE_STARTUP_PATHS
Previously, lfx serve flow.json --workers 4 silently dropped startup flows:
the parent loaded them into a NullFlowStore registry that workers never
inherited. Each worker called create_serve_app() fresh and started empty,
returning flow_count: 0 from /health and 404 for the startup flow.
Fix: before calling uvicorn.run() in multi-worker mode, set
LFX_SERVE_STARTUP_PATHS to the JSON-encoded list of resolved startup file
paths (or the inline-JSON temp file path). create_serve_app() reads this env
var and re-loads the startup flows independently in each worker process using
a ThreadPoolExecutor to avoid RuntimeError from asyncio.run() being called
inside uvicorn's already-running event loop.
Startup flows loaded this way live in each worker's in-memory registry.
Uploaded flows still require --flow-dir for cross-worker sharing (existing
warning preserved). The LFX_SERVE_* env vars are cleaned up after
uvicorn.run() returns via the existing prefix-sweep finally block.
Tests added:
- test_serve_command_sets_startup_paths_env_for_multi_worker: verifies
LFX_SERVE_STARTUP_PATHS is set before uvicorn.run() and cleaned up after
- TestCreateServeAppFactory: unit tests for create_serve_app() in all three
modes (empty start, with startup paths, empty paths list)
* fix(serve): fix 3 worker/store consistency issues
Issue 1 — redundant store writes with --flow-dir + startup files + workers > 1:
When flow_dir is set the parent already persisted startup flows to the store.
create_serve_app() was re-loading and re-writing them in every worker.
Fix: create_serve_app() now skips LFX_SERVE_STARTUP_PATHS when flow_dir is
set and relies solely on warm_from_store(). Similarly, serve_command now only
populates LFX_SERVE_STARTUP_PATHS when flow_dir is NOT set.
Issue 2 — .py restriction was too broad:
The error blocked .py + --workers > 1 even without --flow-dir, where each
worker can safely re-execute the .py via LFX_SERVE_STARTUP_PATHS. The error
now only fires when --flow-dir is also set (workers would skip startup paths
and warm_from_store, missing the .py flow entirely since it can't be stored).
Issue 3 — parent built FastAPI app for multi-worker then discarded it:
create_multi_serve_app() was called before the workers > 1 / else branch,
building a full FastAPI app that was never served in multi-worker mode.
Moved into the else branch so it's only built for single-worker.
Tests:
- test_create_serve_app_with_flow_dir_skips_startup_paths_uses_store:
asserts build_registry_from_paths NOT called when flow_dir is set;
flow comes from store via warm_from_store instead.
- test_create_serve_app_without_flow_dir_loads_startup_paths_from_files:
asserts flow IS loaded when no flow_dir.
- test_serve_command_does_not_set_startup_paths_when_flow_dir_set:
asserts LFX_SERVE_STARTUP_PATHS is [] when flow_dir is set.
- test_serve_command_allows_py_with_multiple_workers_no_flow_dir:
asserts .py + workers>1 + no flow_dir is allowed and path appears in env.
* test(serve): remove duplicate test and fix mislabeled test name
- Remove test_create_serve_app_loads_startup_paths: functionally identical
to test_create_serve_app_without_flow_dir_loads_startup_paths_from_files
(same setup, same assertions, added in a prior session).
- Rename test_verify_api_key_header_takes_precedence to
test_verify_api_key_query_param_takes_precedence: the test passes the
correct key as the query_param argument and the wrong key as header_param,
then asserts the query param value is returned — query param has precedence,
not header. The old name was the opposite of what the test actually checks.
* [autofix.ci] apply automated fixes
* fix(serve): fix concurrent request isolation and startup UX
Three bug fixes discovered via live QA:
1. Component.__deepcopy__ produced shallow copies of _outputs_map,
_edges, and _components, causing concurrent requests to share the
same Output objects across graph copies. Output.cache=True by
default: the first request's output.value was served to all
subsequent concurrent requests, producing identical responses for
different inputs. Fixed by deep-copying all three fields with memo.
2. POST /flows/upload/ without an explicit id used uuid5(content),
which is deterministic — two uploads of any flow with the same
nodes/edges structure always generated the same UUID and the second
always returned 409. Fixed by using uuid4() (random).
3. Startup CLI emitted emoji and exclamation-point decoration in the
serve banner and verbose output. Replaced with plain text.
* [autofix.ci] apply automated fixes
* fix(serve): address PR review comments
- Move 409 conflict check before load_flow_from_json+prepare so callers
with an explicit id don't pay the parse cost for a flow that will be
rejected anyway
- Remove unreachable FlowAlreadyRegisteredError catch: no await exists
between registry.get() and registry.add(), so asyncio cannot
interleave another request in that window
- Update FlowMeta.id and UploadFlowResponse.id field descriptions from
stale "Deterministic UUIDv5" to plain "Flow identifier (UUID)"
* test(serve): fix upload duplicate/replace tests after uuid4 change
Both tests relied on uuid5(content) determinism to generate the same
flow ID from identical data. With uuid4, each upload gets a fresh ID,
so duplicate detection was never triggered. Tests now supply an
explicit id field to exercise the 409/replace paths directly.
* fix(serve): address code review suggestions
- Fix test_write_is_atomic to glob for any *.tmp files rather than
checking a hardcoded filename that could never exist
- Remove incorrect noqa: B904 from validate_id_is_uuid (not in an
except block); use `from None` instead
- Add TTL cache (1 s) for FlowStore.list_ids() in FlowRegistry to
avoid one filesystem glob per /health request; mutations invalidate
immediately, warm_from_store and delete-scan bypass it
- Document the UUID/stem dual-key alias invariant on _flows
- Add test verifying create_serve_app() propagates load errors from
the ThreadPoolExecutor startup path (fail fast, not silent empty registry)
* [autofix.ci] apply automated fixes
* fix: lfx serve no_env_fallback, deepcopy cycle, cross-worker delete (#13297)
fix: address concurrency and isolation issues in lfx serve
- Re-apply no_env_fallback to the per-request graph copy in the run and
stream endpoints; deepcopy() drops graph.context so the flag was lost.
- Register the copy in memo before recursive deepcopy calls in
Component.__deepcopy__ so reference cycles are preserved.
- Check the store directly in list_metas() so cross-worker deletes are
reflected immediately instead of after the TTL cache window.
- Remove the redundant upload-conflict test (already covered by
test_upload_duplicate_without_replace_returns_409).
Also mark test secrets with allowlist pragmas and fix two docstring
lint warnings in the touched test file.
* [autofix.ci] apply automated fixes
* feat(lfx): WXO/TRM request-scoped variables for lfx serve (child of #13121) (#13327)
* feat(lfx): WXO/TRM request-scoped variables for lfx serve global_vars
Align lfx serve with the TRM/WXO credential contract on top of #13121:
- Parse LANGFLOW_REQUEST_VARIABLES from global_vars payloads
- ContextVar-backed lookup in VariableService (no os.environ mutation)
- Activate request scope during execute_graph_with_capture
Child of add-workers-and-upload-lfx (PR #13121).
* [autofix.ci] apply automated fixes
* fix(lfx): harden request-scoped variable resolution + add isolation t… (#13347)
fix(lfx): harden request-scoped variable resolution + add isolation tests
Addresses PR review of WXO/TRM request-scoped variables for lfx serve:
- service: honor no_env_fallback in VariableService so model/KB credential
resolution no longer falls back to os.environ under --no-env-fallback;
correct the get_variable precedence docstring (5-step interleaved order)
- request_scope: add a per-request no_env_fallback ContextVar; use precise
Token type; document the None-vs-empty scope contract
- common: activate/reset the no_env_fallback scope from graph.context
- runtime_variables: log (instead of silently swallowing) a malformed or
non-object LANGFLOW_REQUEST_VARIABLES blob; anchor the override comment to
the TRM contract
Tests:
- add request-scope concurrency isolation + no-env-fallback suite
- add execute_graph_with_capture wiring test (graph.context -> ContextVars)
- give TestGraphExecution mock graphs a real context, fixing 11 tests that
broke once the helper began reading graph.context
* [autofix.ci] apply automated fixes
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jordan Frazier <122494242+jordanrfrazier@users.noreply.github.com>
* Update use of stem alias tracking to ensure proper deletes
* fix(lfx): enforce no_env_fallback across all credential resolution paths
Closes credential-isolation gaps in lfx serve and hardens request-scoped
variable handling:
- VariableService: skip the process-wide LANGFLOW_REQUEST_VARIABLES env read
when env fallback is disabled (previously leaked process credentials for an
empty-global_vars request despite --no-env-fallback).
- Unified models: gate os.environ reads in get_api_key_for_provider and the
connection-config fallbacks in instantiation.py (WatsonX URL/project-id,
Ollama base-url, OpenRouter headers) on the no-env-fallback flag.
- run_until_complete: propagate the caller's contextvars into the worker
thread so request-scoped vars and the flag survive the thread hop.
- Request variables: serialize structured (dict/list) values as valid JSON and
drop nulls instead of producing lossy Python reprs or a truthy "None".
- FlowRegistry: extract the duplicated store-alias scan shared by add/remove.
Adds tests for request-scope isolation, credential/env gating, and contextvar
propagation.
* fix(lfx): close no_env_fallback credential leaks and harden serve isolation
- credentials: honor no_env_fallback in get_all_variables_for_provider so served
flows no longer leak process env (was defeating the _env_if_allowed guards in
instantiation.py via pre-filled provider_vars)
- loading: apply the no_env_fallback contract to table load_from_db column
resolution (sibling of load_from_env_vars, previously still read os.getenv)
- variable service: resolve request-scoped vars (exact name + x-langflow-global-var
alias) before env fallback so a caller's per-request credential is never shadowed
by a same-named process env var
- runtime_variables: flatten the LANGFLOW_REQUEST_VARIABLES blob into graph context
for parity between the ContextVar and load_from_db resolution paths
- flow_store: clean up the partial temp file when an atomic write fails
- commands: on multi-worker shutdown remove only the serve env vars we set, not the
whole LFX_SERVE_* namespace
- components: add check_component_env_writes lint banning os.environ writes; stop the
mem0 component from writing the OpenAI key into the process environment
* fix(lfx): contain corrupt flow-store files so one bad file can't crash worker startup
FilesystemFlowStore.read() only caught FileNotFoundError, so a corrupted/partial
{id}.json in a shared store (PVC corruption, an external writer killed mid-write)
raised JSONDecodeError. warm_from_store() calls get() for every store id at startup
with no per-file guard, so a single bad file would crash every worker's startup and
500 GET /flows.
- read(): catch JSONDecodeError/OSError/UnicodeDecodeError, log, return None (treat
as absent) so warm_from_store/list_metas/get skip it and other flows keep serving.
- warm_from_store(): skip+log a flow that fails to reconstruct (corrupt JSON, or valid
JSON referencing a component not available in this build) instead of aborting startup.
- tests: corrupt-JSON read returns None; warm_from_store skips unloadable flows.
* fix test -- request wins over env now
* fix test. mock graph context correctly
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@logspace.ai>
Co-authored-by: Himavarsha <40851462+HimavarshaVS@users.noreply.github.com>
SimplifiedCodeTabComponent hardcoded a `dark` class, a dark-only
`tomorrow` syntax style, and white label text, so code/JSON/tool-output
blocks in the playground stayed solid black after switching from Dark to
Light theme. Read the active theme from useDarkStore and drive the
container, label, and syntax style from it so the background adapts to
light/dark like other code displays in the app.
* fix: prevent MCP server edit from creating a duplicate on name change
When editing an MCP server, changing the name field caused a brand-new
duplicate server to be created instead of updating the existing one,
leaving the original untouched. It only happened when the name input
was changed.
Root cause: the backend PATCH /mcp/servers/{server_name} endpoint
upserts keyed by the name in the URL path, and the edit modal built the
PATCH URL from the edited name. A changed name therefore retargeted the
request to a new key, so the backend created a new server rather than
updating the original.
The server name is the immutable identifier (the storage key and the
PATCH URL path), so during edit we now:
- always reuse the original name (initialData.name) for the request
instead of re-deriving it from the input, and
- lock the name input, consistent with how the type tabs are already
disabled in edit mode.
Adds regression tests covering the locked name field (STDIO + HTTP) and
that editing patches the original name without firing the create flow.
* fix: immutable server names
* fix: populate _embeddings for upload-ingested KBs in Knowledge retrieve
Knowledge retrieval with `include_embeddings=true` always returned
`_embeddings: None` for any knowledge base populated via direct file
upload.
The retrieval path gathers embedding vectors separately (via
`backend.iter_documents(include_embeddings=True)`) and joins them back
onto the search results. That join was keyed solely on the `_id`
metadata field. Component-driven ingestion stamps a content-hash `_id`
on every chunk, but the direct file-upload path
(`KBIngestionHelper.perform_ingestion`) does not write an `_id` at all.
So for upload-populated KBs the set of wanted ids was empty, the
embedding-gather block was skipped entirely, and every row fell back to
`None`.
Key the join on `_id` when present and on page content otherwise. The
content fallback is exact for this use case — two chunks with identical
text necessarily share the same embedding vector — and the `"id"` /
`"content"` tag namespaces the two key spaces so a mixed KB never
cross-matches. This fixes both existing and newly-ingested KBs across
all backends (the join runs through the shared `iter_documents` API).
Adds regression coverage exercising a real in-process Chroma backend for
the upload-style (no `_id`), component-style (`_id`), and disabled paths,
plus unit tests for the new `_embedding_match_key` helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* fix: opensearch embeddings
* chore: regenerate component index after release-1.10.0 merge
The release branch merge mis-resolved component_index.json, reverting the
Knowledge component's entry (code + code_hash) to the pre-PR source
(4bae415b5feb). Rebuilt from source under Python 3.13 to restore the
correct entry (26a843069b81), matching knowledge.py and the starter flows.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>