* 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>
* 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
* 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>
* 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>
* 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.
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.
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>
* 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.
* 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>
* 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>
* 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>
* 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>
* 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>
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.
* 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.
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.
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.
* 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>
* 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
* feat(LE-906): Config/settings plumbing
- Add 7 new boolean settings: custom_component_admin_only, embedded_mode,
hide_logout_button, hide_new_project_button, hide_new_flow_button,
hide_starter_projects, mcp_servers_locked (all default: false)
- All settings configurable via LANGFLOW_* env vars
- Expose all flags through ConfigResponse schema
- Fix hide_getting_started_progress to read from settings not env
- Add frontend type definitions for all 7 flags
- Initialize utility store with all new flags
- Implement hydration logic for new flags in config hook
* [autofix.ci] apply automated fixes (attempt 2/3)
* fix(LE-906): address PR1 review feedback
- replace internal ICA wording with neutral embedded-mode language
- add settings field for hide_getting_started_progress
- map hide_getting_started_progress from settings in ConfigResponse
- add regression test for embedded_mode hide-flag cascade
- revert unrelated component_index drift from PR scope
* fix(LE-906): address remaining PR1 Gabriel comments
* docs/test(LE-906): clarify embedded_mode scope for QA
- Clarify in settings docs that embedded_mode cascades UI hide flags only
- Keep mcp_servers_locked and custom_component_admin_only as explicit opt-ins
- Add config test assertions that security flags do not auto-cascade
* feat(LE-906): Embedded UI visibility changes
- Hide logout button from account menu when hideLogoutButton flag enabled
- Hide new flow button from header when hideNewFlowButton flag enabled
- Hide new project button from sidebar when hideNewProjectButton flag enabled
- Hide starter projects tab from templates modal when hideStarterProjects enabled
- All UI gates read from utility store (flags populated from server config)
- Supports embedded/iframe mode integration where standalone UI elements are hidden
- Pure frontend changes: no backend dependencies, no breaking changes
- Respects embedded_mode umbrella flag when individual hide flags set
* fix(LE-906): restore overwritten header components and keep embedded UI gates
* [autofix.ci] apply automated fixes
* feat(LE-906): hide new flow entry points
* fix(LE-906): address PR4 review comments from Gabriel
* fix(LE-906): resolve PR4 biome lint failures
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* feat(LE-906): Custom component admin gating + fix field refresh side effect
- Add custom_component_admin_only gate to POST /custom_component endpoint
- Only admin users can CREATE new custom component code when flag enabled
- Remove admin gate from POST /custom_component/update endpoint
* This endpoint handles field metadata refresh (e.g., model provider changes)
* Normal users need access to refresh available models/options
* Gate on creation prevents harmful code, not on field refresh operations
- Use hardened feature-flag checks with getattr(..., False) is True pattern
- Fixes side effect where normal users couldn't refresh field metadata
(e.g., couldn't change LLM models when provider config changes)
- Gate applies only to creation of NEW custom components, not metadata updates
* fix(LE-906): address PR3 review feedback
- remove stale placeholder commentary from custom_component/update
- enforce admin-only restriction for truly custom code updates
- keep known-template refresh/update path available for non-admin users
- add endpoint tests for allow/block behavior under admin-only mode
- revert unrelated component_index drift from PR scope
* feat(LE-906): tighten custom component admin gating
* fix(LE-906): tighten custom-component admin gate carveouts
* [autofix.ci] apply automated fixes
* fix(LE-906): address PR3 review comments from Gabriel
- Rename create-side known-template refresh test for accurate intent
- Strengthen superuser coverage with novel-code create case and update case
- Revert unrelated starter project dependency version drift (OpenDsStar)
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* feat(LE-906): MCP lock enforcement + UI/tests
- Add mcp_servers_locked gate to POST /{project_id}/install endpoint
- Extend lock check to PATCH /{project_id} endpoint for auth settings updates
- Non-superuser requests blocked with 403 when flag is enabled
- Add hardened feature-flag checks using getattr(..., False) is True pattern
to prevent MagicMock truthiness in tests
- Update AddMcpServerModal to show locked message when mcp_servers_locked=true
- Fix JSX structure in modal (missing fragment wrapper)
- All MCP modifications now gated when mcp_servers_locked flag enabled
* fix(LE-906): address PR2 review feedback
- add is_mcp_servers_locked helper with MagicMock-safe semantics and rationale
- add regression tests for explicit-true vs MagicMock placeholder behavior
- add missing mcp.modal.lockedTitle/lockedDescription i18n keys across locales
- remove debug leftover test_page.html
- revert unrelated component_index drift from PR scope
* fix(LE-906): enforce MCP lock in v2 servers
* fix(LE-906): address PR2 review comments from Gabriel
- Remove duplicate is_mcp_servers_locked helper from v1/mcp_projects.py;
import from api/v2/mcp instead so unit tests protect production code
- Retarget test imports to langflow.api.v2.mcp.is_mcp_servers_locked
- Add test_v2_mcp_servers_unlocked_allows_non_superuser_add_patch_delete
to cover the flag-off case (non-superuser can CRUD when gate is off)
* fix(LE-906): make MCP lock configurable in PR2
- Declare mcp_servers_locked in Settings so LANGFLOW_MCP_SERVERS_LOCKED is honored
- Add regression test to assert Settings exposes and reads mcp lock env var
* chore(ci): trigger PR2 CI
* feat(LE-906): Config/settings plumbing
- Add 7 new boolean settings: custom_component_admin_only, embedded_mode,
hide_logout_button, hide_new_project_button, hide_new_flow_button,
hide_starter_projects, mcp_servers_locked (all default: false)
- All settings configurable via LANGFLOW_* env vars
- Expose all flags through ConfigResponse schema
- Fix hide_getting_started_progress to read from settings not env
- Add frontend type definitions for all 7 flags
- Initialize utility store with all new flags
- Implement hydration logic for new flags in config hook
* [autofix.ci] apply automated fixes (attempt 2/3)
* fix(LE-906): address PR1 review feedback
- replace internal ICA wording with neutral embedded-mode language
- add settings field for hide_getting_started_progress
- map hide_getting_started_progress from settings in ConfigResponse
- add regression test for embedded_mode hide-flag cascade
- revert unrelated component_index drift from PR scope
* fix(LE-906): address remaining PR1 Gabriel comments
* docs/test(LE-906): clarify embedded_mode scope for QA
- Clarify in settings docs that embedded_mode cascades UI hide flags only
- Keep mcp_servers_locked and custom_component_admin_only as explicit opt-ins
- Add config test assertions that security flags do not auto-cascade
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
The PyPI langflow-nightly is pip-installable again, so the temporary
`if: false` guard added on 2026-05-28 is no longer needed.
#13418 (release-1.10.0) and its forward-port #13419 (main) gave the
extension bundles their own nightly track: langflow-nightly now depends on
lfx-arxiv-nightly / lfx-duckduckgo-nightly / lfx-ibm-nightly, which pin
lfx-nightly==0.5.0.dev* instead of an unsatisfiable stable lfx>=0.5.0,<0.6.0.
Verified against the published dev57 wheels: a dry-run resolve of
langflow-nightly[postgresql] succeeds (549 packages, exit 0), pulling
lfx-nightly==0.5.0.dev57 via the *-nightly bundle variants.
Previously, on first load with no saved language preference, the app
used navigator.language as the fallback, causing users with Portuguese
(pt-PT / pt-BR) or other non-English browser locales to see the UI in
that language automatically. The expected behavior is that English is
always the default; users can change the language explicitly via
settings.
Co-authored-by: Antônio Alexandre Borges Lima <104531655+AntonioABLima@users.noreply.github.com>
Three name-generation tests monkeypatched `uuid4` on the watsonx_orchestrate `utils` module, but `uuid4` is imported and used in `payloads.py` (build_langflow_wxo_resource_name). Python resolves the name against the defining module's globals, so patching utils raised AttributeError. Point the patches at payloads_module and drop the now-unused utils_module import.
* fix(i18n): translate custom component nodes and fix missing RAG template notes in ja/fr
- Add translate_component_node() helper to i18n.py and call it from
custom_component and custom_component_update endpoints so canvas
interactions (tool mode toggle, field edits) no longer revert node
labels to English when a non-English locale is active.
- Sync ja.json and fr.json from GP: both locales were missing
template_notes.vector_store_rag.* keys, causing the Vector Store RAG
README note to always render in English.
* fix(i18n): translate Toolset output and UpdateAllComponents banner strings
- Extract the shared tool-mode output ("Toolset") via a sentinel norm
"_toolmode" in extract_backend_strings.py so the dynamic output
injected on every component when tool mode is enabled is covered by
a single shared translation key instead of being silently skipped.
- Update translate_component_node() to use the "_toolmode" norm when
the output name is "component_as_tool", resolving the key correctly
for all components.
- Wrap all hardcoded strings in UpdateAllComponents/index.tsx with t()
(summary banner, Dismiss/Dismiss All, Review All/Update All buttons).
- Add 15 new updateComponents.* keys to frontend en.json with i18next
_one/_other plural variants; upload and download all locale files.
- Sync all backend and frontend locale files from GP.
* fix(i18n): sync backend locale files with Toolset translations from GP
* fix(i18n): translate Retry button in server connection error dialog
* [autofix.ci] apply automated fixes
* fix(i18n): inline lfx.base constants in extract_backend_strings to fix CI
The GP script test environment mocks lfx.components but does not install
the full lfx package, so `from lfx.base.tools.constants import ...` raised
ModuleNotFoundError. Inline the two constants directly, matching the pattern
already used in bake_note_keys.py.
* [autofix.ci] apply automated fixes
* fix(i18n): address PR review — blockedPlural scheme, TOOL_OUTPUT_NAME constant, translation error isolation
- I1: Replace {{blockedPlural}} English-suffix hack in UpdateAllComponents with two
separate t() calls (blockedCannotRun + andMustBeUpdated), each driven by its own
count parameter, giving proper _one/_other pluralization for both counts independently.
Remove broken blockedAndMustUpdate_one/other keys from all 6 non-English locales
(de/es/fr/ja/pt/zh-Hans) so they fall back cleanly to English until re-translated
via the GP pipeline.
- I3: Replace magic string "component_as_tool" in i18n.py with TOOL_OUTPUT_NAME
imported from lfx.base.tools.constants — the canonical source of truth.
- I4: Move translate_component_node() calls outside the main try/except in both
custom_component and custom_component_update endpoints; wrap each in a narrow
try/except that logs and falls through to the untranslated node, so an i18n bug
can no longer fail a successful component update with HTTP 400.
* fix(i18n): translate update-components modal strings and preserve custom field display_names
- Translate hardcoded strings in UpdateComponentModal (title, column
headers "Component"/"Update Type", "Breaking" label) — adds 5 new
keys under updateComponent.* and downloads translations for all locales
- Fix custom component field display_names reverting on reload: extend
build_component_display_names to collect all known locale translations
per input field; syncNodeTranslations now checks the known-translation
set before overwriting a field's display_name — user-customized values
(not present in any locale file) are left untouched while standard
translatable values continue to update correctly on locale switch
- Update ComponentDisplayNamesType to include per-field translation sets
* fix(i18n): correct updateComponent.breaking translation key
Change English source from "Breaking" to "Breaking Change" so GP
produces correct translations (all locales were getting "Breaking News"
variants). zh-Hans now correctly shows 重大变更, consistent with the
existing breakingUpdateDesc body text.
* chore(i18n): re-download frontend translations after reverting breaking key to "Breaking"
* fix(i18n): extract and translate FileComponent description
The extraction script silently skipped @property descriptors when reading
description from component classes. Add a fallback to _base_description
for components that use a dynamic @property description.
Unify _base_description with the string used inside get_tool_description()
so there is a single source of truth, then re-run extract/upload/download
to add the missing translations for FileComponent description and Knowledge
component strings across all locales.
* fix(ui): redesign outdated-components banner with title + list layout
Replace long sentence-style summary with bold "Flow needs review" title
and short per-condition bullet lines. Change Dismiss All button from
link to outline variant. Add 3 new i18n keys and sync all locale translations.
* fix(i18n): add missing backend locale translations for de, es, pt, zh-Hans
Regenerated via GP download script after merging release-1.10.0 changes.
* [autofix.ci] apply automated fixes
* fix(i18n): resolve ruff lint errors in i18n utils and endpoints
* fix(lint): suppress SLF001 for internal class attribute access in FileComponent
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* [autofix.ci] apply automated fixes
* starter templates
* fix(tests): update outdated-banner assertions to match redesigned layout
* [autofix.ci] apply automated fixes
* fix(docs): restore accidentally deleted memory-base docs from release-1.10.0
* fix(credentials): restore DB-first API key resolution and tests from release-1.10.0
* [autofix.ci] apply automated fixes
* fix(i18n): revert fetchErrorComponent key to common.retry (out of scope)
* [autofix.ci] apply automated fixes
* chore(i18n): sync backend and frontend locale files from GP
* [autofix.ci] apply automated fixes
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Antônio Alexandre Borges Lima <104531655+AntonioABLima@users.noreply.github.com>
The nightly tagger renames lfx -> lfx-nightly, but the extension bundles
were published as stable lfx-<name> wheels pinning lfx>=0.5.0. So
langflow-nightly depended on the stable lfx-arxiv (etc.), which dragged in
lfx>=0.5.0 -- a version only published under the lfx-nightly name. Installing
the nightly therefore failed:
Because only lfx<=0.4.5 is available and lfx-arxiv==0.1.0 depends on
lfx>=0.5.0, ... your requirements are unsatisfiable.
Give the bundles their own nightly track, mirroring lfx/base/main:
- update_lfx_version.py now renames each src/bundles/* package to
lfx-<name>-nightly, versions it <base>.dev<N> (sharing lfx's dev number),
and repoints the root langflow deps + [tool.uv.sources] workspace entries
at the -nightly names. Each bundle's own lfx dep was already repinned to
lfx-nightly==<dev>. Only the [project] name/version change -- entry points
and the import package stay as lfx_<name>, so extension discovery is intact.
- release_nightly.yml publishes the nightly bundle wheels to PyPI and gates
publish-nightly-main on them, so langflow-nightly's exact == pins always
reference bundles published in the same run.
No build/test/Docker changes needed: the bundles are already built and
committed under the nightly tag, the cross-platform test resolves them via
--find-links, and Docker builds from the regenerated workspace lock.
* 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
---------
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>