Commit Graph

18055 Commits

Author SHA1 Message Date
39e9eacb2f Update version and project name v1.10.0.dev64 2026-06-04 01:10:55 +00:00
b4a8b08414 fix: Warm circular imports to avoid deadlock (#13490) 2026-06-03 15:51:23 -07:00
685d12adab feat: mention canvas components in the assistant input (#13486)
* @-mention canvas components in the assistant input

* fix suggestion width

* add translation

* 70% resizeble chat

* add field search on assistant @

* update docs
2026-06-03 21:20:37 +00:00
ec0b11d721 chore: update lock files (#13483)
* 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>
2026-06-03 17:07:34 -04:00
f1d4db704d fix(frontend): clean up new-flow onboarding, no orphaned placeholders, named templates, assistant tooltip stacking (#13451)
* UI assistant general fixes

* gh suggestions and fix tests

* [autofix.ci] apply automated fixes

* bug fixes pipeline

* [autofix.ci] apply automated fixes

* fix model selection on assistant

* fix ruff and tsx

* fix run flow test

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-03 17:40:43 -03:00
91cf5573b6 chore: Update AGENTS.md authorization guards 2026-06-03 12:38:18 -07:00
984c96a97b docs: firecrawl component (#13487)
docs-firecrawl-component
2026-06-03 18:50:49 +00:00
c84498fffc fix(conditional-router): route Text Input when Case True/False are blank (#13389)
* 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>
2026-06-03 17:51:36 +00:00
2c8b646bb5 fix: enforce mutually exclusive component constraint on paste (#13468)
* 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
2026-06-03 16:57:11 +00:00
71cd5ce376 feat: improve lfx serve production-readiness (#13121)
* 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>
2026-06-03 15:33:39 +00:00
1e0515f019 fix: make playground code block background theme-reactive (#13470)
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.
2026-06-03 15:24:57 +00:00
db21662f92 fix: prevent MCP server edit from creating a duplicate on name change (#13464)
* 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
2026-06-03 14:44:33 +00:00
1d3a186138 chore: prevent UnicodeEncodeError crashing error logging on Windows consoles (#13482)
fix windows testes folder
2026-06-03 11:35:15 -03:00
cc97a32aee chore: lean langflow-base image without bundles 2026-06-02 20:12:46 -07:00
93d283e2df fix: adjust grep for package names 2026-06-02 18:48:12 -07:00
f2d49e5098 fix: tidy up docling extra reqs 2026-06-02 18:20:14 -07:00
7269456232 fix: populate _embeddings for upload-ingested KBs in Knowledge retrieve (#13446)
* 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>
2026-06-02 17:59:26 -07:00
16f733102e docs: wxo deploy updates (#13345)
* align-with-example-video

* docs: move wxo up in sidebars

* include-beta-and-and-ff-warning

* agent-language

* sidebars

* cleanup

* docs-peer-review
2026-06-02 22:27:18 +00:00
7ee0501690 docs: bundle separation and component updates (#13472)
* docs-add-bundles-and-instructions

* docs-text-io-legacy

* docs-add-internationalization-release-note

* docs-remove-unpublished-pages

* docs-use-bash-for-code-blocks-on-sdk-page

* peer-review
2026-06-02 21:31:36 +00:00
2d6321039b fix: Fix timestamp rendering for expires_at in API Key model (#13471)
Fixed timestamp render for expires_at in API Key model to show correct timestamp according to users local timezone.
2026-06-02 20:54:15 +00:00
641c0bcd55 fix: declare kb_allowed_folder_roots on Settings so KB folder ingestion works (#13448)
* fix: declare kb_allowed_folder_roots on Settings so folder ingestion works

The folder ingestion route
(POST /api/v1/knowledge_bases/{kb_name}/ingest/folder) reads
settings.kb_allowed_folder_roots, but that field was never declared on
the Settings model. Because Settings.model_config uses extra="ignore":

- accessing settings.kb_allowed_folder_roots raised AttributeError, which
  the route's generic except Exception re-raised as HTTP 500 — so every
  call to the folder-ingest endpoint failed, on both Chroma and OpenSearch
  backends; and
- the env var LANGFLOW_KB_ALLOWED_FOLDER_ROOTS bound to nothing and was
  silently dropped, so the allow-list could never be configured.

The AttributeError fired before FolderSource.validate_config(), so the
empty-allow-list case never produced its actionable 400.

Declare kb_allowed_folder_roots: list[str] = [] on Settings. The existing
CustomSource handles comma-separated env parsing for list fields, so
LANGFLOW_KB_ALLOWED_FOLDER_ROOTS is now configurable. With the default
empty allow-list the route reaches FolderSource.validate_config() and
returns a 400 with guidance, matching the sibling /ingest/connector route.

Existing endpoint tests injected a mocked settings object
(SimpleNamespace(kb_allowed_folder_roots=...)), so they never exercised
the real Settings model and missed this. Add:
- Settings-model unit tests (field declared, defaults to [], binds
  comma-separated from the env var), and
- an endpoint test that does NOT mock get_settings_service, asserting the
  route returns 400 (not 500) with the real settings.

* Update test_connector_endpoints.py
2026-06-02 13:10:43 -07:00
5120b7705a fix: Move Docling components into bundle (#13442)
* feat: move docling components to bundle

* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]

* chore: wake CI

* fix: trim docling chunking extra

* Update test_endpoints.py

* Update test_pilot_docling_upgrade.py

* fix: show friendly component label in build banner for bundle nodes

Extension-bundle components have namespaced node ids of the form ext:<bundle>:<ClassName>@<slot>-<uuid>. The flow-build status banner rendered that raw id, which overflowed the fixed-width (530px) container and collided with the elapsed-time counter.

Collapse namespaced ids to <ComponentName>-<uuid> (matching the built-in ComponentName-UUID label) via a new getRunningNodeLabel helper; built-in node ids are returned unchanged. Covered by unit tests.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-02 12:33:38 -07:00
91cf9a6936 docs: update vector store rag template (#13291)
* docs: update vector store rag template

* docs: update vector rag template

* fix(starter-projects): repair malformed JSON in Vector Store RAG template

The README noteNode was missing the opening brace after "node":,
producing invalid JSON (Expecting ',' delimiter at line 660). This
crashed scripts/gp/bake_note_keys.py and broke starter-template
loading, cascading into the backend, LFX, and all frontend Playwright
shards. Restore "node": { ... } to match the other note nodes.

* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]

* ci: retrigger checks

---------

Co-authored-by: Eric Hare <ericrhare@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-02 18:10:06 +00:00
28c8fe3deb feat(authz): finalize authz_share — variable share-aware fetch, dedup, tests (#13444)
* feat(authz): finalize authz_share — variable share-aware fetch, dedup, tests

Phase 3 (authz_share) was mostly shipped, but `variable` — though advertised as a shareable resource (ShareResourceType, VariableAction, ensure_variable_permission, owner-lookup) — was the one resource whose routes never became share-aware. They hard-scoped to the caller and passed variable_user_id=current_user.id, so the owner-override always tripped: a permitted non-owner PATCH/DELETE 404'd before enforcement and variable shares were silently dead.

- variable.py: PATCH/DELETE now use authorized_or_owner_scoped and pass the resolved owner to ensure_variable_permission, wrapping plugin-deny as 404 via deny_to_404. Mutations run against the resolved owner so the owner-scoped service queries match. OSS behavior is unchanged (owner-scoped). A missing variable on DELETE is now 404 (was 500), consistent with PATCH.

- authz_shares.py: extract a single _share_visible() predicate shared by get_share and list_shares, removing duplicated owner/PUBLIC/USER/TEAM logic.

- knowledge_bases.py: wire filter_visible_resources into the KB list to match the flows/deployments/projects convention (no-op in OSS; gives plugins the documented per-row hook).

- AGENTS.md: list variable PATCH/DELETE among the share-aware fetch helpers.

- tests: variable cross-user (OSS 404, plugin allow/deny), TEAM-scope share visibility via get_share/list_shares, the _share_visible predicate, and the cache-invalidation contract.

* test(authz): cover cross-user DELETE plugin paths for variables

Mirror the PATCH plugin tests for DELETE: a plugin-allowed non-owner delete removes the owner's row (exercises the owner-scoped delete under the resolved owner), and a plugin-denied delete returns 404 via deny_to_404 with the owner's row intact.

* fix(authz): gate KB disk fallback on raw DB rows, not authz-filtered rows

list_knowledge_bases reassigned 'rows' to the filter_visible_resources result, so if an authorization plugin filtered out all of a user's KB rows, the empty result fell through to the disk fallback (kb_path.exists()) and re-surfaced KBs the plugin had just hidden. Keep the raw DB result as original_rows, filter into a separate 'rows' for the returned list, and gate the disk fallback on 'not original_rows' so it only fires when the user genuinely has no DB rows. Adds a regression test.

* revert(authz): drop inert KB list filter_visible_resources

The KB list only fetches the caller's own rows, and filter_visible_resources lets owner rows bypass batch_enforce, so the call could never narrow anything — pure forward-compat scaffolding that also introduced the disk-fallback edge case. Revert list_knowledge_bases to its owner-scoped form (single-KB fetch stays share-aware). Removes the mock-only regression test that could only cover the now-removed gate. KB share-awareness can be revisited if/when the list grows non-owned candidates.

* test(authz): mark new authz test modules no_blockbuster

Add module-level pytestmark = pytest.mark.no_blockbuster to the variable and share-route test modules, per the project review guideline. No-op today (the blockbuster fixture is autouse=False) but keeps the modules correct if blockbuster is re-enabled repo-wide.

* fix(authz): deny_to_404 surfaces non-403 errors unchanged

The non-403 branch preserved the original status code but relabeled the
detail as "<resource> not found", so any 4xx/5xx raised by a guard would
surface with a misleading "not found" message. Return the original
exception unchanged; only a 403 maps to 404 for UUID privacy. Latent
today (the authz guards raise only 403). Strengthen test_fetch.py to
assert the non-403 detail survives.
2026-06-02 17:41:38 +00:00
e90c87046a docs: clarify local CLAUDE.md hard-rules import behavior (#13467)
import claude from claude folder
2026-06-02 15:06:38 -03:00
2775e4eff9 [autofix.ci] apply automated fixes 2026-06-02 17:06:06 +00:00
aa931407da Update uv.lock 2026-06-02 10:03:04 -07:00
e65ced99cc Merge release-1.9.6 into release-1.10.0 2026-06-02 09:58:24 -07:00
d3f448e528 fix(memories): return 422 instead of 500 when request body is missing (#13452)
fix(memories): return 422 instead of 500 for missing request body

POST /api/v1/memories/{id}/flush returned HTTP 500
({"message":"'ellipsis' object has no attribute 'session_id'"}) when
called with no request body, instead of the 422 it correctly returns for
an empty JSON object.

The body parameters were declared as
`Annotated[Model, Body(embed=False)] = ...`. When no body is sent at all,
FastAPI's body solver leaks the Ellipsis default through as the parameter
value, so the handler runs with `body = ...` and raises
`'ellipsis' object has no attribute 'session_id'` (500).

Per FastAPI's documented practice, a required body declared with
`Annotated` must omit the default rather than set `= ...`. Dropping the
Ellipsis default makes a missing body fail validation cleanly with 422,
matching the empty-`{}` case. Applied to all three affected handlers
(create / update / flush), which shared the same pattern.

Adds HTTP-level regression tests covering missing-body and empty-JSON
requests for the flush and create endpoints.
2026-06-02 15:46:51 +00:00
d8b9473b90 feat: Improve canvas deploy review flow (#13246)
* fix: improve canvas deploy review flow

* feat(deploy): update canvas replace dialog

* fix(deploy): align replace dialog styling

* fix(deploy): polish deploy modal i18n

* fix(deploy): restore review label contrast

* fix(frontend): use accent-amber-foreground token in deploy review

Replace hardcoded text-amber-600/dark:text-amber-400 with the existing
accent-amber-foreground semantic token to satisfy the no-hardcoded-tailwind-palette
Biome plugin.
2026-06-02 15:24:48 +00:00
7760f5a0e6 fix(stepflow): route multi-output fan-out to the correct step per connection (#13412)
A Langflow component whose multiple outputs fan out to different downstream
nodes previously routed the same (first-edge-wins) output to every consumer.
`_build_output_mapping_from_edges` keyed the selected output by source node id
alone, so all but the first outgoing edge silently inherited the wrong output.

Each Langflow node maps to one Stepflow step whose executor runs a single
method (chosen by `selected_output`) and returns one `result`, so correct
routing requires materializing one step per distinct fanned-out output:

- Key the output mapping per (source, target) connection and derive the
  distinct outputs each source fans out.
- Materialize one step per distinct output (the primary keeps the unsuffixed
  step id; additional outputs get unique suffixed ids), and wire each consumer
  edge to the step matching its specific output, falling back to the primary
  ref for single-output sources.
- Apply the same edge-aware resolution to the ChatOutput and workflow-output
  paths so a non-primary output feeding ChatOutput is no longer lost.

Single-output flows are unchanged (one step per node). Adds regression tests
covering custom-code and core-executor fan-out, ChatOutput on a non-primary
output, single-output step reuse, and the edge-scoped mapping helpers.

Fixes #12308

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:48:44 +00:00
3dbfe9fdb8 docs: lf assistant flow building example (#13445)
* docs: assistant example

* add-prereq-for-custom-components

* Apply suggestions from code review

Co-authored-by: Mendon Kissling <59585235+mendonk@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com>

---------

Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com>
2026-06-02 14:45:51 +00:00
a41aa3a4bf feat: add custom comp execution override (#13177)
* Add custom comp exeuction override flag

* fix(settings): handle comma-separated LANGFLOW_COMPONENTS_PATH in override enforcement

CustomSource splits comma-separated env vars into individual list entries before
the field validator runs, so the raw env var string was never found in
self.components_path when multiple paths were specified. Split the env var by
comma and strip each entry individually. Add a test covering the multi-path case.

* update doc

* fix(settings): scope components_index_path stripping to env-sourced value

Mirror the components_path handling and only clear components_index_path
when it matches LANGFLOW_COMPONENTS_INDEX_PATH, addressing review feedback
on the override enforcer.
2026-06-02 14:28:44 +00:00
3ade9aaeed test: fix flaky Windows Playwright tests for error popups, settings nav, and output inspection (#13463)
fix user setting test flaky on windows
2026-06-02 10:26:43 -03:00
551270dd40 fix(memory): show dashes for stat cards in All Sessions view (#13443)
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
2026-06-02 10:39:02 +00:00
43f1aa45ba fix(logging): keep JSON level field clean when stdlib level names are mutated (#13457)
* fix(logging): keep JSON level field clean when stdlib level names are mutated

Third-party libraries (e.g. ibm_watsonx_orchestrate_core) call
logging.addLevelName() at import time to wrap stdlib level names in ANSI
color codes. structlog's ProcessorFormatter derives a foreign record's
level from record.levelname.lower(), so in file-mode JSON logging the
mangled, colored string (e.g. "\x1b[0;33m[warning]\x1b[0;0m") landed
verbatim in the JSON `level` field, breaking level-based filtering in
Grafana/Loki.

Derive the level from the immutable record.levelno instead, mirroring the
stdout-mode InterceptHandler which was already robust. Add a shared
_levelno_to_structlog_name() helper as the single source of truth and a
add_stdlib_log_level_from_record() processor for the file-mode
foreign_pre_chain.

Adds regression coverage that simulates the global addLevelName mutation
and asserts clean `level` fields on both the file and stdout paths
(restoring level names in teardown so it cannot re-pollute the suite).

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]

* fix(logging): fingerprint all inputs in configure() early-return

configure()'s early-exit compared only the resolved log level, so a second
call at the same level but a different log_env / log_file / log_format /
output_file / disable silently no-opped: the file handler was never
installed and the renderer never switched. This was a real footgun (e.g.
lfx.run.base re-points output_file at fixed levels) and a source of
test-isolation flakiness -- a prior same-level configure() made a later
file-mode configure() do nothing, surfacing as FileNotFoundError when a
test read a log file that was never written.

Resolve all env-var fallbacks up front and gate the early-return on a
fingerprint of every effective input (resolved level, log_env, log_file,
log_format, disable, log_rotation, cache, output_file). The fingerprint is
stored on the wrapper_class so structlog.reset_defaults() invalidates it
between tests. The optimization is preserved for genuinely identical calls
(the per-Graph hot path in Graph.__init__).

Adds TestConfigureEarlyReturnFingerprint: a same-level call with a new
log_env+log_file now installs the file handler and writes JSON (this assertion
fails on the old code), output_file changes take effect, and identical calls
still early-return.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-01 19:58:55 -07:00
affa44f487 Merge release-1.9.5 into release-1.10.0 2026-06-01 16:51:35 -07:00
30c9aaef30 feat: mark Text Input and Text Output components as legacy (#13420)
* feat: mark Text Input and Text Output components as legacy

Chat Input already handles text input natively, and the current API
paradigm forwards all terminal node outputs automatically, making the
Text Input and Text Output components redundant.

Mark both as legacy and point users to their modern replacements:
- TextInput  -> ChatInput  (replacement = ["input_output.ChatInput"])
- TextOutput -> ChatOutput (replacement = ["input_output.ChatOutput"])

Migrate starter templates off the Text Input component:
- Knowledge Retrieval: convert the search-query Text Input to Chat Input
- Blog Writer, Instagram Copywriter, Portfolio Website Code Generator,
  Twitter Thread Generator: inline preset Text Input values directly into
  their target Prompt/LLM fields and remove the now-redundant input nodes

Also drop stale root-level width/height from non-note nodes in Basic
Prompting, fixing a pre-existing test_width_height_at_node_level failure.

Update .secrets.baseline line numbers for the edited starter templates
(component code_hash false positives shifted by the node changes).

* [autofix.ci] apply automated fixes

* test: enable legacy toggle in Playwright specs that drag Text Input/Output

Text Input and Text Output are now legacy, so they are hidden from the
component sidebar by default (showLegacy=false). Specs that drag them from
the sidebar must first enable the legacy toggle via the existing
addLegacyComponents() helper (as freeze.spec.ts already does).

Add the helper call to the 8 specs that added Text Input/Output without it,
fixing the Playwright shard failures on this PR.

* test: fix Blog Writer starter spec for inlined instructions field

Blog Writer no longer has a separate "Instructions" Text Input node; the
value is now an inlined field on the Prompt component (Text Input is legacy).
Fill the Prompt's instructions field (textarea_str_instructions) instead of
the removed node's textarea_str_input_value.

* test: force clicks past legacy banner overlap in similarity & fileUpload specs

Marking Text Input/Output legacy adds a "Legacy" warning bar that increases
node height. In these two tightly-packed layouts the bar/body now overlaps an
adjacent node's handle/button and intercepts the click. Force the affected
clicks past the overlay (consistent with existing force-click usage in
fileUploadComponent).

Fixes the Shard 34 (similarity) and Shard 43 (fileUpload "use text input for
file paths") failures introduced by the legacy marking.

* test: dismiss legacy warning bars instead of force-clicking through them

The previous force-click fix was wrong: Playwright's force click still
dispatches the event at the target's coordinates, which the browser routes to
the topmost element there (the overlapping "Legacy" warning bar), so the real
click never reached the covered handle/button (similarity's inspection modal
never opened).

Add a dismissLegacyWarnings() helper and call it before the affected
interactions in similarity and fileUpload. Dismissing the bars removes the
extra node height, restoring the compact pre-legacy layout so the normal
click lands on its intended target.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-01 22:42:51 +00:00
44d43dbeaf fix: re-validate redirects against SSRF protection in API Request component (#13394)
* fix: re-validate redirects against SSRF protection in API Request component

The API Request component validated only the initial URL and pinned DNS for
the initial host, then let httpx auto-follow redirects when follow_redirects
was enabled. With SSRF protection enabled, a public URL that redirected to an
internal address (loopback, RFC1918, link-local / cloud metadata) was followed
without re-validation, bypassing the SSRF block.

When SSRF protection is enabled, redirects are now followed manually and every
hop's Location is re-validated with the same private/loopback/link-local
denylist and DNS pinning applied to the initial request. Redirects are capped
(MAX_REDIRECTS=20), non-http(s) schemes are blocked, relative locations are
resolved, and sensitive headers (Authorization / Cookie) are dropped on
cross-host redirects. make_request now defaults to follow_redirects=False
(fail-safe). Behavior is unchanged when SSRF protection is disabled.

Adds regression tests for public->localhost, public->RFC1918, public->link-local
metadata, scheme changes, chained redirects, redirect to a hostname resolving
internal, per-hop DNS pinning, the redirect cap, and cross-host header stripping.

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)

* [autofix.ci] apply automated fixes

---------

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>
2026-06-01 15:05:13 -07:00
69982462bb fix: add SSRF protection to legacy RSS Reader and SearXNG components (#13395)
* fix: add SSRF protection to legacy RSS Reader and SearXNG components

The legacy RSS Reader (data_source/rss.py) and SearXNG tool (tools/searxng.py)
called requests.get() directly on user-controlled URLs without any SSRF
validation, bypassing the protection applied to the url and api_request
components. With SSRF protection enabled (the default), an authenticated user --
or an LLM agent via the tool_mode RSS URL input -- could make the server fetch
internal addresses such as cloud metadata (169.254.169.254), loopback, or
RFC1918 hosts and read the response.

Add a shared ssrf_safe_get() helper for the synchronous requests library that
validates the initial URL and re-validates every redirect hop with
validate_url_for_ssrf(). requests follows redirects by default, so validating
only the initial URL is insufficient; the helper disables automatic redirects,
resolves relative Locations, blocks non-http(s) schemes, and caps redirects.
RSS Reader and both SearXNG request sites now use it. Validation is a no-op when
SSRF protection is disabled, so behavior is unchanged for that configuration.

Adds unit tests for the helper (direct internal IP, cloud metadata,
redirect-to-internal, public redirect chain, relative redirect, scheme change,
redirect cap, protection-disabled passthrough) and component regression tests
for RSS Reader and SearXNG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [autofix.ci] apply automated fixes

* Coderabbit finding

* [autofix.ci] apply automated fixes

---------

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>
2026-06-01 15:04:10 -07:00
181ed3771d fix: restrict builtins and validate code in Python Interpreter components (#13397)
* fix: restrict builtins and validate code in Python Interpreter components

PythonREPLComponent.get_globals() built the exec globals from the global_imports
allow-list but never set __builtins__. CPython's exec() then auto-injects the full
builtins module, so __import__, open, eval, exec and the whole import machinery
stayed reachable regardless of the allow-list -- e.g.
__import__("subprocess").check_output([...]) -- making the "Only modules specified in
Global Imports can be used" claim false. The same get_globals() pattern existed in the
legacy PythonREPLToolComponent.

Add a shared lfx.utils.python_repl_security module:
- safe_builtins(): a curated __builtins__ allow-list that keeps common safe builtins
  (print, len, range, container/number types, exceptions, ...) and removes
  __import__/eval/exec/compile/open/input/globals/locals/vars/getattr/setattr/...
- validate_code_safety(): an AST check rejecting inline imports, dunder attribute
  access (the ().__class__.__subclasses__() escape), frame/traceback introspection,
  mro(), and str.format("{0.__globals__}") template traversal.

Both components now set __builtins__ via safe_builtins() and validate the sanitized
code (PythonREPL.sanitize_input) before exec. The tool component builds a fresh globals
namespace per invocation so state cannot leak across tool calls. The core component's
default Global Imports is reduced from "math,pandas" to "math" so the default no longer
bundles a deserialization sink (pandas.read_pickle); pandas can be added back explicitly.

This is defense-in-depth hardening, not a guaranteed sandbox -- Python sandboxing is
hard; the primary control for untrusted access remains authentication.

Adds unit tests for safe_builtins/validate_code_safety and component regression tests
(import bypass, subclasses/frame/format escapes and inline imports blocked; legitimate
code and whitelisted modules still work; per-invocation isolation; default excludes pandas).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [autofix.ci] apply automated fixes

---------

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>
2026-06-01 21:17:28 +00:00
75efd0a2a4 fix: point file manager empty-state link to the files page route (#13450)
The "My Files" shortcut in the file manager empty state linked to a
top-level "/files" route that does not exist. The files page is nested
under "assets" (routes.tsx: assets -> files), so clicking the link
dead-ended instead of opening My Files.

Point the link at "/assets/files", matching the sidebar navigation in
sideBarFolderButtons (_navigate("/assets/files")). Add a regression test
asserting the empty-state link's href.
2026-06-01 20:50:40 +00:00
e2d17a6d84 feat(logging): production-grade structured logs for Grafana/Loki (#13164)
* feat(logging): production-grade structured logs for Grafana/Loki

Make langflow and lfx log output viable for ingestion by Grafana/Loki and
other observability tools when run in JSON mode (LANGFLOW_LOG_ENV=container).

Core changes in src/lfx/src/lfx/log/logger.py:
- Preserve exceptions in JSON output via structlog.processors.ExceptionRenderer
  with ExceptionDictTransformer. Tracebacks now emit as a structured exception
  array (exc_type, exc_value, frames) instead of being dropped.
- show_locals defaults to OFF; opt-in via LANGFLOW_LOG_TRACE_LOCALS=true so
  frame locals can't leak API keys, env, or request bodies in shipped logs.
- Add service metadata (service / version / environment) from
  LANGFLOW_SERVICE_NAME / LANGFLOW_VERSION / LANGFLOW_ENVIRONMENT.
- Add logger name to every record so Grafana can filter by source.
- Add optional OpenTelemetry trace_id / span_id correlation. Import is
  resolved once at module load; runtime calls are wrapped so a flaky tracer
  SDK can never break logging.
- Add default-on PII redaction for password, token, api_key, authorization,
  cookie, etc. Walks nested dicts, lists, and tuples up to depth 4. Extra
  keys via LANGFLOW_LOG_REDACT_KEYS.
- Add per-logger level overrides via LANGFLOW_LOG_LEVELS="name=LEVEL,...".
  Malformed entries (typos like WARN instead of WARNING) raise UserWarning
  instead of silently dropping.
- Use ISO 8601 UTC timestamps.
- Install a stdlib InterceptHandler on the root logger in JSON modes so
  uvicorn, sqlalchemy, httpx, langchain, asyncio etc. emit a single unified
  JSON stream. Forwards exc_info and stack_info. emit() is wrapped to route
  any error through handleError so a malformed third-party log call cannot
  raise into the request path. Install is idempotent: re-running configure()
  updates the level instead of stacking handlers. Not installed in pretty
  mode so dev terminals don't get duplicate lines.
- Reset cached loggers at the start of configure() so modules that captured
  a logger before configure() ran pick up the new processor chain.
- Preserve the get_logger() name through PrintLogger so add_logger_name can
  attach it.

Tests in src/backend/tests/unit/test_logger.py:
- Cover structured tracebacks, PII redaction (top-level, nested, list,
  tuple, depth limit), logger name, stdlib intercept forwarding exc_info
  and stack_info, intercept idempotency, intercept-not-installed in pretty
  mode, malformed-args safety net, service info defaults and env overrides,
  malformed LANGFLOW_LOG_LEVELS warning, container_csv exception text,
  show_locals default off (verified by absence of the secret value, not
  just the key) and opt-in.

* docs(observability): Grafana + Loki reference stack and env-var docs

Adds a self-contained Loki + Promtail + Grafana docker-compose stack under
deploy/observability/grafana-loki/ with a pre-provisioned dashboard that
demonstrates the production logging features: structured tracebacks, PII
redaction, service/version/environment labels, and the stdlib intercept
path. Anyone running Langflow in JSON mode can point Promtail at their log
file and get a working board on first run.

Also documents the new env vars (LANGFLOW_SERVICE_NAME, LANGFLOW_VERSION,
LANGFLOW_ENVIRONMENT, LANGFLOW_LOG_LEVELS, LANGFLOW_LOG_REDACT_KEYS,
LANGFLOW_LOG_TRACE_LOCALS) in docs/docs/Develop/logging.mdx and adds a
new page docs/docs/Develop/observability-grafana-loki.mdx covering JSON
output shape, structured exceptions, stdlib routing, and OpenTelemetry
trace correlation. Registered in the Observability sidebar category.

* docs(observability): rename dashboard to 'Langflow Logs'

* docs(observability): fix broken link from logging guide to Grafana/Loki page

The logging guide linked to the new Grafana/Loki page with an absolute
path (/observability-grafana-loki). Since the page is new and only
exists in the next docs version, the absolute link resolved to a
non-existent root-version route and failed the Docusaurus broken-link
check. Use a version-aware relative .mdx link instead.

* docs(observability): clarify stdout requirement for unified JSON logs

The Grafana/Loki guide told users to set LANGFLOW_LOG_FILE, but the stdlib
intercept that routes uvicorn/sqlalchemy/httpx/langchain into the JSON stream
is only installed on the stdout path, so those library logs landed in the file
as plain text and the json parse stage could not label them. Point the guide at
stdout (redirected to the scraped file) and note the file-mode limitation.

Also document that the JSON format is platform-agnostic and works with any
JSON-ingesting backend including IBM Instana, whose OpenTelemetry-based Python
tracer (3.0+) correlates logs to traces via trace_id/span_id.

* fix(logging): render stdlib logs as redacted JSON in file mode

In JSON mode with LANGFLOW_LOG_FILE set, the stdlib intercept was skipped to
avoid a recursion loop, so third-party loggers (uvicorn, sqlalchemy, httpx,
asyncio) wrote plain text straight to the file. Those lines bypassed both JSON
rendering and PII redaction, so secrets in their structured fields landed in the
file verbatim and Loki could not parse or label them.

Route JSON file output through a structlog ProcessorFormatter on the rotating
handler: foreign stdlib records are enriched via foreign_pre_chain (ExtraAdder +
redaction) and rendered as JSON alongside application logs, while the
RotatingFileHandler keeps log rotation. The stdout path is unchanged. Also
forward stdlib extra fields through InterceptHandler so the stdout path redacts
them too, keeping both paths consistent.

Update the Grafana/Loki deploy guide to reflect that LANGFLOW_LOG_FILE now
produces a single redacted JSON stream.

* fix(logging): retrieval buffer captures the message text

add_serialized stored the rendered message under the 'message' key, but
SizedLogBuffer.write only read 'event'/'msg'/'text', so every entry returned by
the /logs and /logs-stream endpoints had an empty message. Read 'message' first
and keep the other keys as fallbacks for records written in other shapes.
2026-06-01 20:23:31 +00:00
fb046c02df fix: ibm db2 bulk insert pr 13438 (#13449)
* feat(ibm): add IBM Db2 Vector Search component with comprehensive tests

- Add Db2VectorStore component with vector search capabilities
- Implement search methods (similarity, MMR, similarity with score)
- Add comprehensive test suite covering all search operations
- Enhance db2vs.py with improved error handling and connection management

* fix(ibm): preserve DB2 bulk insert values

---------

Co-authored-by: Dhruv Chaturvedi <dhruv_insights@mac.lkw-in.ibm.com>
2026-06-01 13:38:34 -07:00
cc6cdf9297 fix(tracing): warn clearly when LangWatch is unavailable on Python 3.14 (Docker) (#13410)
fix(tracing): warn clearly when LangWatch is unavailable on Python 3.14

The official Docker images run Python 3.14, but the `langwatch` package caps
its requires-python at <3.14, so it is excluded from the image. With
LANGWATCH_API_KEY set, the tracer hit an ImportError and disabled itself with
only a cryptic, per-run `logger.exception`, so users saw no traces and no
actionable explanation (works on PyPI/desktop, which run Python 3.10-3.13).

Emit a single, version-aware warning when the langwatch package is missing
despite the API key being set, and document the Python 3.14 / Docker
limitation in the LangWatch integration guide.
2026-06-01 19:27:43 +00:00
ad54146c9f fix: Remove Playwright from Dependencies (#13382)
Remove Playwright from Dependecies

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
2026-06-01 19:12:25 +00:00
9779214c6d fix(db): hold migration advisory lock for create_db_and_tables too (#13393)
The advisory lock added in #13204 only wrapped run_migrations, but
initialize_database calls create_db_and_tables first. That path runs
table.create(checkfirst=True) for every SQLModel table, which is a
SELECT-then-CREATE that's not atomic. Concurrent workers booting against
a fresh Postgres still race here: all three see no job_status_enum, all
three issue CREATE TYPE, and the losers fail with UniqueViolation on
pg_type_typname_nsp_index. The error is re-wrapped as RuntimeError
"Error creating table job" before initialize_database's "already exists"
swallow ever runs, so the worker exits with code 3.

Wrap create_db_and_tables in the same _postgres_migration_lock. On
Postgres the DDL now runs in a worker thread so the contended-lock poll
does not block the event loop; SQLite preserves the original async path
(no advisory locks). Extract _normalize_sync_postgres_url so the lock
helper, the new locked DDL path, and check_postgresql_version_sync stop
duplicating the same async-to-sync URL normalisation.

Verified end to end: against postgres:16-alpine with a fresh volume,
N=3/4/6/8 concurrent processes calling initialize_database all complete
cleanly. Without this change the same setup deterministically fails 2/3
workers with the UniqueViolation above.
2026-06-01 19:01:02 +00:00
7e321059db docs: redis worker queue (#13386)
* docs: remove 1.8 env vars

* docs: link out to deployment guide

* docs: redis queue

* fix-links-and-combine-env-vars-table

* docs: cleanup

* peer-review

* peer-review

* Apply suggestions from code review

Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com>

* move-prereqs

---------

Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com>
2026-06-01 16:35:25 +00:00
73e49b13a0 test: Skip starter-projects shard4 on Windows CI (#13439)
skip shard 4 for windows
2026-06-01 09:56:11 -03:00
3e57126b91 fix: remove duplicate uv.lock from workspace member (#13326)
* fix: remove src/backend/base/uv.lock and Dockerfile references

- Deleted src/backend/base/uv.lock (monorepo should have only one uv.lock at root)
- Removed COPY ./src/backend/base/uv.lock lines from all Dockerfiles:
  - docker/build_and_push.Dockerfile
  - docker/build_and_push_base.Dockerfile
  - docker/build_and_push_ep.Dockerfile
  - docker/build_and_push_with_extras.Dockerfile
  - docker/dev.Dockerfile

Fixes LE-1093

* fix: remove stale base uv.lock regeneration paths
2026-05-30 13:05:34 +00:00