`PreToolValidationWrapper.convert_langchain_tools_to_sparc_tool_specs_format`
degrades to a minimal tool spec when a tool can't be introspected, but its
`except` tuple omitted `RuntimeError`. On Python 3.14 + langchain-core 1.4.7,
reading `tool.args` for a tool whose `args_schema` is not a usable model (e.g. a
`@property` that shadows the inherited Pydantic field) returns the raw property
object, so langchain's `get_all_basemodel_annotations` recurses on
`get_origin(<property>) -> None -> None -> ...` and raises `RecursionError` (a
`RuntimeError` subclass) -- which escaped the handler and crashed conversion
(surfaced by test_altk_agent_tool_conversion.py::test_error_handling).
Add `RuntimeError` to the caught tuple, matching the two sibling handlers in the
same module that already catch it. This restores the intended fall-back to a
minimal spec and hardens real agent runs against the same recursion.
FastAPI 0.137 made `include_router` lazy: included sub-routers are stored as
`_IncludedRouter` wrappers (no `.path`) in `app.routes` instead of being
flattened. `opentelemetry-instrumentation-fastapi` names spans by walking
`app.routes` and reading `route.path`; its `Match.FULL` branch already guards a
missing `.path`, but the `Match.PARTIAL` branch does not. A partial match -- e.g.
an OPTIONS/CORS preflight against a GET-only route -- raised
`AttributeError: '_IncludedRouter' object has no attribute 'path'` mid-request and
turned into a 500 (surfaced by test_security_cors.py::TestCORSIntegration).
`instrument_app` hard-codes the span-detail callback and exposes no override hook,
so patch the module-level `_get_route_details` (resolved as a global on every call)
with a guarded copy that falls back to the include prefix, then the request path,
when the matched route has no `.path`. Applied in `create_app` before
`FastAPIInstrumentor.instrument_app`; idempotent and a no-op on FastAPI <=0.136.
Follow-on to #13710 (same lazy-include root cause, different consumer).
The recent uv update bumped FastAPI 0.136.3 -> 0.137.1 (Starlette 1.2.1 ->
1.3.1), which changed `include_router` to lazy inclusion: an included sub-router
is now stored as an internal `_IncludedRouter` wrapper instead of eagerly
flattening its `APIRoute` objects onto the parent. Anything that introspects
`app.router.routes` no longer sees nested routes.
Production impact: `_get_route_keys` (plugin conflict-protection) iterates
`app.router.routes` to build the reserved set before loading plugins. With the
core routers now behind `_IncludedRouter` wrappers, the reserved set was empty,
so a plugin could silently shadow core Langflow routes. Fix by descending
through the wrapper via its public `original_router` / `include_context`
(accumulating the include-time prefix), so discovery works on both eager
(<=0.136) and lazy (>=0.137) inclusion.
The same assumption broke route-introspection tests, now made version-agnostic:
- test_deployments_routes.py: read routes from the router via a flattener
- test_extensions_route_guard.py: `collect_paths` descends through the wrapper
These failures surfaced on release-1.10.1 and are unrelated to any PR content.
* fix: accept Data and Message inputs in LoopComponent
LoopComponent.input_types only listed DataFrame and Table, rejecting
connections from Data- and Message-producing components at build time.
The class description and _convert_message_to_data method already
intended to support these types.
- Add Data and Message to input_types on the data HandleInput
- Add types=["Data"] to Item output, types=["DataFrame", "Table"] to Done output
- Convert Message to Data in _validate_data before calling validate_data_input
Fixes#13636
* fix: normalize mixed Message/DataFrame lists in Loop._validate_data
Addresses the CodeRabbit review on #13646. The list branch converted
Message items to Data but left DataFrame items untouched, so a mixed
[Message, DataFrame] input became [Data, DataFrame] — which
validate_data_input rejects because DataFrame is not a Data subclass.
Normalize each list item: convert Message via _convert_message_to_data and
expand DataFrame/Table to its rows via to_data_list(), so any accepted input
shape (including a mixed list) yields a homogeneous list[Data].
Add lfx regression tests covering the input/output type metadata, the
connect-time type-compatibility check, and _validate_data across single
Message/Data/DataFrame, list[Message], the mixed-list case, and rejection
of lists with non-coercible items.
* chore: regenerate Loop component index, starter project, and locale
Rebuild the component index, the "Research Translation Loop" starter
project, and the en.json locale string for the Loop input_types / output
types / info changes on release-1.10.1.
* chore: trigger CI re-run
---------
Co-authored-by: dymux <putramkti@users.noreply.github.com>
Co-authored-by: Eric Hare <ericrhare@gmail.com>
A model invoked as the root LangChain run (no wrapping chain) — reproduced
with Ollama — was emitted by the langfuse v3 CallbackHandler as a separate
orphan trace: parent=None, userId=None, sessionId=None, with the token usage
detached from the flow trace, breaking cost/usage attribution.
Root cause: the SDK only applies the constructor `trace_context` on the chain
path (`on_chain_start`); the generation path calls `start_observation` without
it, so with no active OpenTelemetry span the generation starts a brand-new
trace (metadata `is_langchain_root: true`).
`get_langchain_callback` now returns a `CallbackHandler` subclass that, for
root LLM runs only (`parent_run_id is None`), activates the flow's component
(or root) span as the current OTel span while the SDK creates the generation.
The generation then inherits the flow `trace_id` and nests under the component
span, restoring user/session attribution and token metrics. Non-root runs
(wrapping chain/agent present) are left untouched.
Adds focused unit tests plus an end-to-end test that drives the real langfuse
SDK with an in-memory OpenTelemetry exporter and asserts the generation shares
the flow trace_id and parents under the component span.
The dep upgrade in 9266b08e5e bumped uuid to ^14.0.0, which ships ES
modules only ("type": "module"; its sole node export, dist-node/index.js,
is ESM). Jest's CommonJS runtime cannot parse it, so the only suite that
imports uuid (useGetFlowId.test.ts) failed to run with "SyntaxError:
Unexpected token 'export'", failing the Frontend Jest Unit Tests job.
Add a moduleNameMapper entry routing uuid to a project mock, mirroring how
the repo already handles other ESM-only packages (vanilla-jsoneditor,
@jsonquerylang/jsonquery). The mock is RFC 4122-correct (crypto.randomUUID
for v4, deterministic SHA-1 v5 / MD5 v3 with namespace constants), so output
is byte-identical to the real package.
* fix: run trusted server copy on custom-component hash-gate match (#13496)
When allow_custom_components=False (or admin-only mode), the /custom_component
and /custom_component/update endpoints gated arbitrary code execution solely on
a 48-bit truncated SHA-256 (sha256(code)[:12]) match against public built-in
component hashes. A second-preimage collision (feasible in minutes on commodity
hardware) let an authenticated user clear the gate with attacker-controlled
bytes that were then exec'd — an RCE in the very multi-tenant hardening mode the
flag is meant to provide.
Fix: once the hash gate passes in restricted mode, execute the server's trusted
copy keyed by that hash instead of the client-submitted bytes, failing closed if
no trusted source is found. A collision now merely re-runs the server's own
known-good component. The 12-char hash is intentionally left intact as the
component-index key (widening it would force a full index regeneration); the
security decision simply no longer trusts client bytes.
- collect_code_by_hash() / get_trusted_code_for_validation() in
lfx.utils.flow_validation (the collector drops any source whose recomputed
hash != its key, so a poisoned index entry can't smuggle in code)
- ComponentCache.code_by_hash, populated with the existing hash lookups and
invalidated on bundle reload
- substitution in both custom_component handlers, gated by
_requires_component_hash_lookups
Verified on the real 355-component index that all_known_hashes == code_by_hash
keys, so legitimate known-template refreshes never hit the fail-closed path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: return trusted copy in custom-component update response (#13496)
In restricted mode the /custom_component/update endpoint executed the
server's trusted copy (effective_code) but rebuilt its response template
from the raw client bytes. Because the saved node's code field is what
instantiate_class exec's at flow-build time, a colliding payload echoed
back into a saved flow could be re-executed on the build path, bypassing
the substitution. Write effective_code into the returned template instead
(no-op in default mode, where effective_code == code_request.code).
Add tests: update-endpoint collision case (asserts trusted code is
returned and the attacker sentinel never fires) and a fail-closed 403
case (gate passes but no trusted copy exists).
* fix(frontend): align jest jsdom environment with jest-runtime 30.4.x
The lockfile had jest-runtime and the @jest core packages at 30.4.2 while
the jsdom environment cluster (jest-environment-jsdom, @jest/environment,
@jest/fake-timers, @jest/environment-jsdom-abstract, jest-mock) stayed
pinned at 30.3.0. jest-runtime 30.4.x calls
moduleMocker.clearMocksOnScope() during resetModules, and that method
only exists in jest-mock 30.4.x. Because the jsdom environment built its
ModuleMocker from the stale jest-mock 30.3.0, every Jest suite crashed at
load with "this._moduleMocker.clearMocksOnScope is not a function"
(384 suites failed, 0 tests run).
Bump the jsdom-environment jest cluster to 30.4.1 so the environment's
ModuleMocker matches jest-runtime. No package.json range changes; the
only non-jest delta is a transitive nwsapi patch bump pulled in by jsdom.
https://claude.ai/code/session_01J8U3i2WH2863cDuwVu9awa
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: guard event serialization against unserializable component payloads (#12591)
Loop flows containing a vector store (e.g. Chroma) failed to build with:
Error building Component Loop:
[TypeError("'_thread.lock' object is not iterable"),
TypeError('vars() argument must have __dict__ attribute')]
The Loop runs its body in an isolated subgraph and streams an on_end_vertex
event for every completed inner vertex. EventManager.send_event serialized
that payload with a raw fastapi.jsonable_encoder call. When a payload carries
an object jsonable_encoder cannot serialize -- most notably a threading.Lock
held by a vector-DB client -- its last-resort fallback raises
ValueError([TypeError, TypeError]), which propagated out of the subgraph and
aborted the whole flow build.
The earlier fix (#12877) only guarded ResultData.validate_model and
build_output_logs (both already routed through the fail-safe serialize()), so
this jsonable_encoder path was never covered and the issue persisted.
send_event now falls back to lfx's fail-safe serialize() when jsonable_encoder
raises, degrading unknown objects to a string representation instead of
crashing. The happy path is unchanged (the fallback only runs on failure).
Adds a focused EventManager unit test and an end-to-end Loop regression test
whose body emits a lock-bearing Data (both fail without the fix).
* [autofix.ci] apply automated fixes
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix(security): run trusted server code on unauthenticated public flow builds
The unauthenticated public build path (POST /api/v1/build_public_tmp/{flow_id}/flow)
builds a public flow as its owner and executes its components, running each node's
stored `code` via eval_custom_component_code. The custom-component hash gate
(validate_flow_for_current_settings) is a no-op under the default
allow_custom_components=true, so a public flow containing a plain CustomComponent —
or any node carrying arbitrary code — would execute on that path without
authentication (follow-up to H1-3754930).
Enforcing the hash gate on the public path is not viable: it rejects legitimate
flows whose built-in component code has merely drifted across versions ("outdated"),
which would break the public playground on every upgrade.
Instead, on the public path substitute the server's trusted code for each known
component type and reject nodes whose type is not a known server component:
- known type -> stored code replaced with the server's current code, so version
drift cannot break the build and a relabelled node cannot smuggle in its bytes;
- unknown/custom type carrying code -> rejected (400);
- codeless nodes (group/note) untouched; inlined sub-flows handled recursively.
The build then runs from this server-sanitized data. Operators who knowingly want
public flows to run custom component code can restore the prior DB-loaded build with
LANGFLOW_ALLOW_PUBLIC_CUSTOM_COMPONENTS=true (default false).
Authenticated builds are unaffected.
* Update src/lfx/src/lfx/utils/flow_validation.py
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* test(settings): add allow_public_custom_components to composition field list
The public-flow trusted-code fix added the allow_public_custom_components
setting to SecuritySettings, which bumped Settings.model_fields to 147 and
tripped test_field_count_unchanged (147 != 146). Add the field to the
curated EXPECTED_FIELDS set under a 1.10.1 section.
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
fix(database): resolve Alembic log to writable config dir, never crash on read-only FS
The default Alembic migration-log path ("alembic/alembic.log") resolved a
relative path against the installed package directory, then opened it for
writing with no error handling. On a read-only filesystem -- a standard
Kubernetes hardening posture (non-root pod / read-only root filesystem) and
the default for read-only container images -- this raised an unhandled
OSError ([Errno 30] Read-only file system) during startup, aborting the
FastAPI lifespan and putting the pod into CrashLoopBackOff.
Root cause: writing mutable runtime state into the package install location
(expected to be immutable). Every other writable Langflow artifact resolves
to config_dir/cache_dir; only the Alembic log resolved into site-packages.
Changes:
- Resolve relative alembic_log_file paths against the writable config_dir
(platformdirs user cache dir, always created on startup) instead of the
read-only package directory. Absolute paths and stdout mode are unchanged.
- Add _open_alembic_log_buffer(): create the parent dir, then fall back to
stdout (with a warning) if opening the diagnostic log raises OSError. This
is the actual crash point in _run_migrations, which runs before
initialize_alembic_log_file(), so guarding only the latter was insufficient.
- Make initialize_alembic_log_file() swallow OSError for the same reason.
The migration log is diagnostic-only and must never abort startup. Existing
deployments that set LANGFLOW_ALEMBIC_LOG_FILE or LANGFLOW_ALEMBIC_LOG_TO_STDOUT
are unaffected.
Fixes#11143
The Lint Frontend job runs against `main` as the base. In a release
(workflow_dispatch) run it diffs the entire release branch (~915 files) and
re-lints nearly the whole frontend, exposing two issues that per-PR linting
never hits together:
1. xargs split starter-project spec paths containing spaces (e.g.
"News Aggregator.spec.ts" -> "News" + "Aggregator.spec.ts"), producing
`internalError/io: No such file or directory`. NUL-delimit the file list
so spaces are preserved. (supersedes #13381)
2. The whole-branch diff surfaced 30 pre-existing Biome errors: 22
noExplicitAny + 8 organizeImports. Resolved with real types where safe
(freezeObject generic, ColDef defaults, messagesSorter field shape,
VertexBuildTypeAPI, Record<string,string>, DragEvent<HTMLElement>,
unknown for narrowed values) and justified biome-ignore for genuinely
loose cases (polymorphic display values, test global stubs, captured
unexported StreamCallbacks). Imports auto-sorted via biome.
Verified locally: biome check on the full release-vs-main file set is now 0
errors (was 30); tsc unchanged at its 303-error baseline (no new type errors).
* fix(components): follow HTTP redirects in URL component with per-hop SSRF revalidation
The URL component sent every request with follow_redirects=False, so any
site whose entered URL 301s to its canonical address (http->https or
www/non-www normalization) returned the redirect stub - e.g. a bare
"301 Moved Permanently / nginx" page - as the scraped content, or failed
outright when the redirect response had an empty body.
Redirects are now followed via a new advanced "Follow Redirects" input
(default on). When SSRF protection is enabled, hops are followed manually
and each Location target is re-validated with the same blocked-IP denylist
and DNS pinning as the initial request before any connection is made,
mirroring the API Request component; cross-host hops drop sensitive
headers and chains are capped at 20 redirects.
* fix(components): compare full origin when keeping credentials across redirects, crawl from post-redirect base
Addresses CodeRabbit review:
- _headers_for_redirect now keeps Authorization/Proxy-Authorization/Cookie
only for same-origin hops (scheme, host, port) or a direct http->https
upgrade on default ports, exactly the cases where httpx keeps the
Authorization header. Applied to both the URL and API Request
components (the helper was copied from the latter).
- _crawl_recursive resolves relative links and the prevent_outside check
against the final post-redirect URL, and marks it visited, so depth>1
crawls work on sites that 301 to their canonical address.
* chore: regenerate component index and starter projects for release-1.10.1 base
The autofix.ci jobs uploaded but never pushed the regeneration after the
rebase, leaving the 4 URL-component templates with a stale field_order
(missing follow_redirects) and the index without the new code hashes.
Generated with the same commands CI uses: LFX_DEV=1 make
build_component_index + scripts/ci/update_starter_projects.py.
Pokedex Agent / Structured Data Analysis Agent pick up the API Request
component's new code_hash from the origin-comparison fix.
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
fix(test): gate models.dev background refresh out of tests
Integration tests failed twice in nightly run 27260425158 with pyleak
EventLoopBlockError - first Integration Tests 3.14, then 3.12 on the
rerun, each time in a different test. The blocking stack points at
refresh_models_dev_periodically: every app boot unconditionally starts
a lifespan task that immediately fetches https://models.dev/api.json,
so the request lands mid-test in whatever test happens to be running.
Under pyleak's asyncio debug instrumentation the fetch blocked the loop
0.797s against a 0.2s threshold. Whichever test draws the short straw
flakes - which is why it looked transient and moved between versions.
Add a LANGFLOW_MODELS_DEV_REFRESH env gate (default unchanged: enabled)
and disable it session-wide in the backend test conftest. Tests fall
back to the bundled static model lists, which is also deterministic.
Verified: with the gate set, app boot makes zero models.dev requests;
the previously failing integration test passes.
fix(test): raise spawn-child join timeout in test_multi_process_visibility
The test spawns a child via multiprocessing spawn context, which
cold-imports the full langflow package (plus coverage's multiprocessing
hooks in CI) before appending a single event. The 10s join timeout is
routinely exceeded on a loaded CI runner sharing 4 vCPUs with a second
xdist worker: in nightly run 27253229568 (Unit Tests - Python 3.12 -
Group 5) the test failed all 12 executions (5 reruns x 2 step attempts),
each rerun exactly 10s apart - the join deadline, not a product bug.
Raise the liveness bound to 60s (join returns immediately when the
child exits, so the passing case is unaffected) and kill the child on
timeout so a hung spawn can't leak into later tests.
fix(ci): rebalance backend test splits with measured durations + raise unit-test step timeout
Unit Tests - Python 3.12 - Group 3 has been failing at 98-99% on the
nightly: the job hits the nick-fields/retry 40-minute per-attempt timeout
and pytest is SIGTERM'd mid-test, so it looks like one flaky last test
when it is actually a deterministic timeout (and the internal retry plus
run-level retries can never succeed).
Root cause is twofold:
1. .test_durations was last regenerated ~May 2025 and covered only 2,219
of ~9,500 current unit tests, so pytest-split weighted 77% of the
suite at the 0.73s average. The expensive client-fixture tests
(test_webhook.py, test_login.py - each pays a full create_app +
lifespan boot per test, 60-120s late in a CI run) clustered into
group 3's tail, making it ~8-10 minutes slower than its siblings
(33:15 on 3.13, >40min on 3.12 which is additionally slowed by
astrapy disabling SSL connection reuse on Python 3.12.0-11).
The weekly store_pytest_durations workflow that should refresh the
file has been dying at the 6-hour GitHub job limit every week (serial
full-suite run no longer fits), so the file silently froze.
This regenerates the file from real per-test wall-clock measured in
the passing Python 3.13 nightly jobs (run 27246228762 attempt 1, all
5 groups, parsed from the -vv xdist logs: per-worker start-to-start
deltas). 9,508 tests now have measured durations; old entries are
kept where no new measurement exists. Simulated least_duration
split goes from one outlier group to 5 even groups (~24.6 min each)
with the >50s tests spread 1-2 per group instead of 7 in one.
2. timeout_minutes 40 -> 50 gives headroom for matrix-cell variance
(3.12 runs ~20% slower than 3.13) so a slightly slow cell degrades
gracefully instead of burning 2x40min and failing the whole run.
Follow-ups (not in this PR): fix store_pytest_durations to run with
xdist or split groups so it fits the 6h limit; investigate the in-worker
degradation that makes client-fixture boots cost 8-12s early in a run
but 60-120s after ~30 minutes.
Companion to the release-1.11.0 fix (#13574). The 1.10.1 fork bump left component_index.json's version at 1.10.0; _read_component_index fails closed on the exact-version mismatch, so the bundled registry loads as None and the upgrade-gate LFX tests fail on this branch's CI.
Surgical restamp: version -> 1.10.1, sha256 recomputed with the build script's exact hashing/serialization. Entries unchanged; 2-line diff. The three affected tests pass locally.
* docs: record nightly→stable bundle cutover plan (gated on lfx 1.10.0)
Add src/bundles/NIGHTLY.md documenting why langflow-nightly currently
renames the bundles (lfx and lfx-nightly ship the same lfx/ import, so a
stable bundle would co-install both and collide) and the deferred cutover
(Approach A: canonical pre-releases; B: lfx as a bundle extra), gated on
stable lfx 1.10.0 being published to PyPI.
Also expand two docstrings in scripts/ci/update_lfx_version.py to state
the deeper install-conflict reason, not just the resolve failure. No
behavior change.
* feat(ci): nightly Approach A — canonical pre-releases, drop nightly bundles [DRAFT/gated] (#13529)
feat(ci): nightly Approach A — canonical pre-releases, drop nightly bundles
DRAFT reference implementation of the nightly→stable-bundle cutover documented
in src/bundles/NIGHTLY.md. Publishes the nightly under CANONICAL package names as
.devN pre-releases instead of separate *-nightly distributions, so the stable
lfx-* bundles resolve against a single canonical lfx (no dual-lfx install
collision) and no nightly bundle packages are produced.
- tag scripts (pypi/lfx/sdk_nightly_tag.py): count .devN against the canonical
PyPI histories instead of the *-nightly projects
- update scripts: stop renaming to *-nightly; set .devN versions; re-pin
inter-package deps to exact canonical dev versions; delete the bundle
rename/repin (update_lfx_dep_in_bundles, rename_bundles_for_nightly)
- release_nightly.yml: publish canonical pre-releases; remove bundle build,
dist-nightly-bundles artifact, publish-nightly-bundles job + its gate; verify
canonical names; main wheel glob dist/langflow-*.whl
- nightly_build.yml: drop the bundle git-add in the tag commit
- NIGHTLY.md: Approach A marked implemented + activation gate + A1/A2 follow-ups
Held as DRAFT: do not activate until stable lfx 1.10.0 is published AND the
nightly base is the next minor (release-1.11.0). A 1.10.0.devN core sorts below
1.10.0 and would fail the bundles' >=1.10.0 floor. Stacked on #13528.
(.secrets.baseline: incidental line-number shifts for the two workflows + prune
of pre-existing stale Pokédex Agent.json entries.)
* docs(ci): drop internal 'Approach A' label from nightly cutover comments
Comment- and docstring-only change across scripts/ci/* and the two nightly
workflows; no logic change. The two workflow edits stay single-line so
.secrets.baseline line numbers are unaffected. src/bundles/NIGHTLY.md keeps
its A/B decision-record framing intentionally.
* feat(ci): make nightly consumers work with canonical pre-releases
Follow-ups from the nightly cutover that are part of its blast radius (the
nightly now publishes canonical `.devN` pre-releases, not `*-nightly`
distributions):
- version.py: derive the "Nightly" label from the `.dev` version marker, since
the canonical `langflow`/`langflow-base` distribution matches first in the
lookup. Keeps the startup banner and telemetry `package` field identifying
nightlies. Adds a canonical-dev test; updates the base-dev assertion.
- ci.yml check-nightly-status: query the canonical `langflow` project and pick
the latest `.devN` release date instead of `langflow-nightly`'s `.info.version`.
- db-migration-validation.yml: install the nightly as `langflow[postgresql]==<dev>`
(pre-release) instead of `langflow-nightly[...]`; verify via version("langflow").
- src/lfx/README.md: nightly install is `uv pip install --pre lfx`.
- NIGHTLY.md: rewrite the follow-ups section (these are addressed; Docker image,
A2 meta-package, and website docs remain deferred by design).
The `langflowai/langflow-nightly` Docker image name is intentionally unchanged.
* fix(ci): correct nightly verify uv tree parsing + stale base-dep regex
Addresses review of #13528:
- release_nightly.yml LFX verify: `uv tree | grep lfx | head -n1` matches the
bundle `lfx-ibm` first → 'Name lfx-ibm does not match lfx'. Root the tree with
`uv tree --package lfx` so the first line is the lfx package itself.
- release_nightly.yml base verify: under canonical naming `langflow-base` prints
as a top-level `langflow-base v<ver>` line, so the old $2/$3 field parse read
name="v0.10.0" and version="". Use `uv tree --package langflow-base` and $1/$2.
- update_lf_base_dependency.py update_base_dep: regex only accepted ~=/==, so its
CLI entry point couldn't match the current root dep `langflow-base[complete]>=0.10.0`.
Add >= (parity with update_uv_dependency.py). The active nightly path uses
update_uv_dependency.py and was unaffected.
* docs(nightly): point NIGHTLY.md status at the activation gate, not draft state
Per review of #13528: this file ships inside #13528 (#13529 was folded in), so
the 'stacked on prep #13528' + 'held as a draft' framing is stale and misleading.
Reword the status block to state the real guard is the activation gate (stable
lfx 1.10.0 published AND next-minor base), not merge/draft state. Also reword the
follow-ups heading 'decide before un-drafting' -> 'decide before activating'.
* fix(security): remove the disabled Python Code Structured tool component
Follow-up to #13538, which neutered PythonCodeStructuredTool to a
non-executable stub "for one release cycle, full removal later." This
completes that removal.
- Delete the component and its registration in lfx.components.tools.
- Drop its entry from the component index (num_components 355 -> 354,
sha256 recomputed surgically) and from stable_hash_history.json.
- Remove its 18 i18n keys from every locale file.
- Replace the dedicated stub unit test with a removal test in
test_dynamic_import_integration.py.
- Add a regressions entry to regressions/1.10.x.yaml.
The unauthenticated public-build RCE fix (report H1-3754930) is
unaffected: PythonCodeStructuredTool stays in
CODE_EXECUTION_COMPONENT_TYPES, so build_public_tmp still rejects any
saved or crafted flow that carries the type. instantiate_class execs the
node's stored `code` field regardless of whether the class still exists,
so the type-name block -- not the class -- is what closes the path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: document removal
* docs: typo
* Apply suggestion from @mendonk
Co-authored-by: Mendon Kissling <59585235+mendonk@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Mendon Kissling <59585235+mendonk@users.noreply.github.com>
* fix(bundles): bump lfx-* bundles to 0.1.1 to republish with corrected lfx pin
The published 0.1.0 artifacts on PyPI carry stale lfx pins from before the lfx 0.5.0->1.10.0 version realignment:
- lfx-arxiv, lfx-duckduckgo: lfx>=0.5.0,<0.6.0 (hard-broken; the <0.6.0 cap can never resolve lfx 1.10.0)
- lfx-docling, lfx-ibm: lfx>=0.5.0 (uncapped floor/cap mismatch)
The source pin was already corrected to lfx>=1.10.0,<2.0.0 in #13516, but the bundles were never re-published. PyPI versions are immutable, so shipping the fix requires a version bump. Bump all four to 0.1.1 so the Release Bundles workflow cuts fresh wheels carrying the correct pin. Root pyproject keeps its >=0.1.0 floors (satisfied by 0.1.1); only the bundle dist versions and their uv.lock stamps change.
* fix(release): relax bundle lfx floor for pre-release builds + idempotent bundle publish
The RC pre-release run builds lfx as 1.10.0rc0, but bundles floor lfx at >=1.10.0. Under PEP 440 a pre-release sorts below the final, so 1.10.0rc0 fails >=1.10.0 and the cross-platform install test cannot resolve the bundle wheels against the RC lfx wheel.
build-base/build-main/build-lfx already rewrite their inter-package deps to the pre-release version when pre_release=true; build-bundles was the only release artifact missing that step. Add it: when pre_release=true, rewrite each bundle's lfx floor to the exact pre-release version, keeping the wide <2.0.0 BUNDLE_API cap. Stable source stays at >=1.10.0 -- only RC wheels are relaxed, at build time, so no source churn or re-tag.
Also make publish-bundles tolerate 'already exists' duplicate wheels on rerun, matching release_bundles.yml.
* fix(security): block code-execution components on unauthenticated public flow builds (H1-3754930)
PythonCodeStructuredTool exec()'d its attacker-controlled `tool_code` field at
flow-build time. Because a PUBLIC flow can be built with no authentication via
POST /api/v1/build_public_tmp/{flow_id}/flow, that sink was reachable as an
unauthenticated server-side RCE — and other components (Python Interpreter/REPL,
Smart Transform) execute code on the same path, so removing one component alone
would not close the gap.
- Harden the public build path: build_public_tmp now rejects flows containing
code-execution components via validate_public_flow_no_code_execution(). The
check keys on the node `type`, so it holds regardless of the stored component
`code`, and is enforced ONLY on the unauthenticated public path —
authenticated /build is unchanged.
- Neuter PythonCodeStructuredTool to a non-executable compatibility stub: all
exec()/eval() sinks removed; build_tool returns a tool that raises a
deprecation error. The component stays registered with identical
display_name/inputs so saved flows still load and locale keys don't change.
It will be fully removed in a future release.
component_index.json (code_hash) is regenerated by autofix.ci.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* fix: validate_public_flow_no_code_execution
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix: serialize DataFrames in tool mode to prevent pandas truncation
When Memory Base or Knowledge components are wired as agent tools, the DataFrame
result was returned directly without serialization. LangChain would then stringify
this for the agent observation using pandas' default repr, which truncates all
cells to 50 characters (display.max_colwidth=50), inserting "...".
This breaks the F3 agent use case where agents need to reliably recall facts from
memory. The agent receives truncated content and cannot see the full text.
The fix serializes DataFrames through the existing serialize() function, which
converts them to list[dict] format with full, untruncated content. This maintains
consistency with how other result types (Message, Data, etc.) are handled in tool mode.
- Affects: Memory Base and Knowledge components used as agent tools
- Does not affect: Component-to-component wiring or normal (non-tool) execution
- Testing: Added tests verifying DataFrames serialize to list[dict] with complete content
Improved MB GUI component description
* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]
* [autofix.ci] apply automated fixes
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* refactor: remove getenvvar component and update locales
* test: add test for GetEnvVar component removal and update component index
---------
Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local>
fix: use shadcn canvas colors for playground code blocks and chat-bg for the container
Standardize the playground tool-call surfaces on shadcn theme tokens so
they swap correctly between light and dark mode:
- Code blocks (SimplifiedCodeTabComponent) use bg-canvas: light grey
(#F4F4F5) / dark black (#000) so they stand out as the code surface.
- The 'Called tool' container (ContentBlockDisplay) uses bg-background so
it matches the chat background in both themes instead of inverting to
solid black in dark mode (it was bg-primary-foreground).
Previously the container and code blocks were both bg-primary-foreground,
which rendered the whole section black in dark mode with no contrast.
* perf(telemetry): batched off-pool writer for transactions + vertex_builds
Adds TelemetryWriterService that buffers transaction and vertex_build rows
in memory and drains them in batched INSERTs via a dedicated AsyncEngine
(pool_size=1 for SQLite, 2 for Postgres, max_overflow=0). Retention is
amortized in a 60s sweeper instead of running on every insert.
Producers (log_transaction, log_vertex_build) enqueue instead of opening
a DB session, so telemetry traffic no longer competes with the
request-handling pool. Falls back to the legacy direct-write path via
LANGFLOW_TELEMETRY_WRITER_ENABLED=false.
Durability: in-flight rows spill to a diskcache.Deque per PID on shutdown
and are restored on next startup; orphan PID directories left by crashed
workers are adopted.
* perf(telemetry): tighten error handling and add coverage
Review feedback from pr-review-toolkit + silent-failure-hunter:
- Retention sweep snapshots the dirty-flow sets before commit and only
clears them after it lands; a crashed sweep no longer drops the flows on
the floor, so per-flow caps cannot drift unboundedly.
- Producer fall-through is no longer silent. transaction_service and
log_vertex_build each log a one-shot WARNING when telemetry_writer_enabled
is True but the writer is not running.
- Lifespan startup failure now logs ERROR instead of WARNING — if the user
opted into the writer and it didn't come up, that's an error.
- Shutdown drain timeout no longer suppressed silently: logs a WARNING with
pending row count + a hint to raise telemetry_writer_shutdown_drain_s.
- Writer's flush loop catches asyncio.CancelledError separately and
re-prepends the in-flight batch to the buffer so teardown's disk spill
catches it.
- After 6 consecutive batch failures the writer emits a loud ERROR with
buffer depths so operators see sustained data-loss risk.
Added tests:
- test_sanitization_survives_writer_round_trip
- test_retention_failure_preserves_dirty_flows
- test_in_flight_batch_returned_on_cancel
* perf(telemetry): address copilot review
- _restore_from_disk + _adopt_orphan_outboxes now route through _enqueue so a
large disk-spilled or orphan outbox can't bypass telemetry_writer_max_queue
and OOM the process. Oldest rows are dropped and counted via the existing
dropped_transactions / dropped_vertex_builds counters.
- chmod 0o700 the outbox root + per-PID directory so sanitized-but-still-
sensitive payloads aren't exposed cross-user on multi-tenant hosts.
Suppressed on platforms where chmod is a no-op (Windows).
- Added test_adopt_orphan_outboxes_honors_max_queue.
The remaining two copilot notes (private API access to DatabaseService and
diskcache.Cache.close) were also flagged by the in-tree review; tracking
separately. The "diskcache not declared" note is a false positive — the
dependency is at src/backend/base/pyproject.toml:76.
* perf(telemetry): address coderabbit review
- Sweeper hands off dirty sets via capture-and-clear so concurrent
flushes during a retention pass aren't wiped by the post-commit
subtract; failure path restores the snapshot.
- Stress README uses a concrete Postgres DSN matching the docker
example instead of an unset env var.
- Test PID-probe loops bounded via a shared helper with pytest.fail
fallback.
* perf(telemetry): swap diskcache outbox for stdlib sqlite (CVE-2025-69872)
Replaces the diskcache.Deque-backed spill outbox with a stdlib sqlite3
outbox (WAL mode, JSON payloads in TEXT). diskcache 5.6.3 has
CVE-2025-69872 (pickle deserialization RCE for an attacker with write
access to the cache dir), no fixed version released, and was never
declared as a dependency — import failed on Python 3.10.
The replacement is encapsulated in a small _Outbox helper that owns the
connection, schema, JSON codec, and lifecycle. The codec uses tagged
wrappers for datetime and UUID so SQLAlchemy's typed columns accept
restored payloads on the way back out.
Hardening informed by a survey of OTel collector, Fluent Bit, Vector,
Prometheus remote_write, Datadog agent, Logstash, and Filebeat:
- Per-PID outbox dirs now stamp an owner.json (hostname + Linux boot_id
or time()-monotonic() proxy). Adoption only proceeds when host+boot
match; cross-host or pre-owner-file dirs are logged and skipped so a
recycled PID after a container restart cannot pull in a stranger's
spill data.
- PRAGMA synchronous=FULL on the spill connection so the shutdown
commit hits the platter (NORMAL only fsyncs on WAL checkpoint, which
may never run if the process exits immediately after commit).
- Spill honors telemetry_writer_max_queue with drop-oldest, matching
the producer-side overflow policy so a backlogged buffer at shutdown
can neither stall teardown nor fill disk.
- append_all encodes payloads up front and only clears the deque after
the transaction commits — no more partial-drain on mid-flight SQLite
failure. drain collapses to a single DELETE FROM outbox after the
SELECT.
- Exception handlers at the spill/restore/adopt boundaries narrowed to
(sqlite3.Error, OSError) so genuinely unexpected exceptions
propagate rather than being silently logged.
Tests cover: cross-host orphan skipped, missing-owner orphan skipped,
spill cap drops oldest, and a realistic UUID+datetime payload
surviving spill → restore → SQLAlchemy INSERT.
* [autofix.ci] apply automated fixes
* fix(telemetry): name wait_for inner tasks so pyleak can filter
Integration tests using @pyleak_marker were flagging an inner asyncio
task created by the telemetry writer's `wait_for(Event.wait(), ...)`
loops. The wrapper task is auto-named (Task-N) and lands mid-await
across the test boundary, so pyleak counts it as leaked. The writer
itself shuts down cleanly via teardown; the "leak" is a pattern
mismatch with pyleak's per-test snapshot model, not a real lifecycle
bug.
Wrap Event.wait() in an explicitly named task (`telemetry-writer-tick`,
`telemetry-writer-backoff`, `telemetry-sweeper-tick`) via a small
_wait_or_shutdown helper, and extend pyleak_marker with a task_name_filter
that excludes `telemetry-*` from leak detection. CI was green on earlier
commits in this branch only because the diskcache import error
prevented the writer from starting at all — fixing the import surfaced
this latent pyleak collision.
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* test(telemetry): disable writer in tests so reads see writes synchronously
* perf(telemetry): age out cross-host orphan outboxes on shared volumes
A pod on a shared PV (NFS/RWX) cannot adopt rows from a dead pod on a
different host because the dead pod's hostname doesn't match. Without a
janitor those directories accumulate forever. Add an owner-file mtime
heartbeat from the sweeper plus a prune pass that deletes cross-host
outboxes whose owner file hasn't been touched within
telemetry_writer_orphan_max_age_s (default 1h). Same-host orphans still
flow through the existing adoption path.
* perf(telemetry): add byte-aware flush + drop strategy
Today the writer bounds memory by row count only, so a worker logging fat
vertex_build artifacts can hold tens of MB per row and silently dwarf the
configured max_queue. Add a size_strategy switch ('count' | 'bytes' |
'either', default 'count' for parity) with two byte thresholds:
- batch_size_bytes (256KB) caps per-flush INSERT size when bytes apply
- max_queue_bytes (200MB) drops oldest by bytes when bytes apply
Parallel sizes deques mirror the payload buffers so accounting stays
consistent across drain, spill, restore, and the cancel/retry rollback.
Single rows above the byte budget are still emitted (no row is refused);
operators get dropped_*_bytes counters alongside dropped_*.
* test(telemetry): tighten byte-strategy assertions and cover end-to-end paths
Address gaps in the byte-aware strategy tests:
- pin exact dropped counts and remaining buffer state for the drop-by-bytes
case (was 'greater than zero')
- compute exact drain count from encoded size (was a 1-4 range)
- round-trip bytes through spill+restore to verify size deque is rebuilt
- round-trip bytes through orphan adoption for the same reason
- exercise the sweeper loop end-to-end to confirm heartbeat + prune are
wired in (previously only unit-tested in isolation)
Clarify in the settings docs that the byte caps measure encoded JSON size,
not Python in-memory footprint.
* ref: updates to telemetry writer PR (#13294)
* fix(telemetry-writer): harden error handling and add missing test coverage
Critical:
- teardown() now re-raises CancelledError after awaiting the writer task so the
asyncio cancellation chain propagates correctly on lifespan task kill
- suppress(OSError) on owner file write replaced with explicit error log so
operators know when disk-spilled rows will not be recoverable on restart
High / important:
- Escalation threshold check changed from == to >= so the error log fires on
every failure past the threshold, not just the 6th
- Dead `except OSError` on time.time() - time.monotonic() changed to
`except Exception` since time functions cannot raise OSError
- Removed redundant `from uuid import UUID` inside _run_retention_pass (already
imported at module top)
- Orphan directory cleanup: replaced blanket suppress(OSError) with per-child
suppress so ENOTEMPTY on an individual rmdir doesn't abort the whole loop and
leave the parent directory leaking silently; outer failure now logs at debug
Tests (3 new):
- test_retention_sweep_caps_vertex_builds_per_vertex: inserts 8 builds for a
single vertex with max_per_vertex=3 so the per-vertex DELETE subquery
actually executes (previous test used 8 distinct vertex IDs, bypassing it)
- test_either_strategy_trips_on_bytes_first: verifies bytes can be the first
trigger under 'either' strategy (previous test only covered the count-first path)
- test_writer_retries_on_batch_failure: injects 2 flush failures then success;
confirms failed_batches increments, rows are preserved in the buffer, and
flushed_rows reflects the final successful write
* ci: add stress-tests job to nightly build pipeline
Wires the stress-tests workflow into nightly so telemetry write stress
tests run automatically. Also gates release-nightly-build and
slack-notification on stress-tests result so a stress regression blocks
the nightly release and surfaces in Slack.
* ci: run stress tests in nightly without blocking release
Stress tests are informational for now — a failure is visible in the
workflow run and Slack but does not gate the nightly release or build.
* fix(telemetry-writer): address PR review on cancelled teardown and nightly stress tests
- Add the stress-tests.yml reusable workflow (was untracked, so the
nightly stress-tests job could never resolve)
- Notify Slack on stress-tests failure (add to slack-notification gate
and FAILED_JOB detection as non-blocking)
- Run sweeper cancel, disk spill, and engine dispose in a finally so a
cancelled teardown() still persists the in-memory buffer
- Add tests for the cancelled-teardown spill path and the >= escalation
threshold; drop the misleading wait-loop in the retry test
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jordan Frazier <122494242+jordanrfrazier@users.noreply.github.com>
* fix: commit slider value before node selection consumes the interaction
Sliders inside React Flow nodes (e.g. the URL component's Depth field) lost
the value the user set when the node was not yet selected. React Flow selects
a node on click and pans/drags it on pointer down, while Radix drives the
slider with the same pointer events. The interactive SliderPrimitive.Root had
no React Flow isolation, so the first interaction on an unselected node was
consumed by node selection: the slider reacted visually but the chosen value
reverted or snapped to wherever the pointer landed.
Stop pointer/click propagation on the slider root and add React Flow's
nodrag/nopan/noflow/nowheel opt-out classes (matching the slider's value-text
input). Radix composes the handlers, so value setting is unaffected.
Adds a regression test asserting slider pointer-down/click do not bubble to
the node wrapper while unrelated children still do.
* Update src/frontend/src/components/core/parameterRenderComponent/components/sliderComponent/__tests__/slider-node-selection.test.tsx
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* [autofix.ci] apply automated fixes
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix(i18n): translate missing frontend strings found in QA
- API Keys page: replace hardcoded "Never" with t("settings.apiKeys.never")
- New Flow screen: migrate all WELCOME_* string constants in
flow-builder-welcome.tsx to t() calls; remove string constants from
flow-builder-welcome.constants.ts (keep WELCOME_MAX_INPUT_LENGTH)
- Get Started sidebar card: add useTranslation and replace 5 hardcoded
strings (All Set, Get started, Star repo, Join the community, Create a
flow) with existing sidebar.* i18n keys
- en.json: add settings.apiKeys.never + 10 flowBuilderWelcome.* keys
- All non-English locale files (ja, fr, es, de, pt, zh-Hans) updated via
GP upload/download
* fix(i18n): translate additional QA-identified untranslated strings
- Translate canvas assistant banner ("Try the new Langflow Assistant!", "New" pill)
- Translate search placeholder flow type name (was showing raw "flows"/"mcp" in mixed-language)
- Translate UNNAMED tool badge in ToolsComponent and ToolsTable
- Extract all 61 flow default descriptions to i18n keys (flow.defaultDescription.0-60)
so new flows get a localized random description instead of always English
- Fix backend ja.json: "Vector Store RAG" → "ベクターストア RAG" for consistency
with the frontend welcome screen translation
- Run GP upload+download to populate fr, ja, es, de, pt, zh-Hans locales
* [autofix.ci] apply automated fixes
* fix(i18n): fix CI failures — Biome import order and updated test constants
- Fix Biome import sort in flow-builder-welcome.tsx (lucide-react after react)
- Update flow-builder-welcome test to use inline English strings instead of
importing the deleted WELCOME_* constants from flow-builder-welcome.constants
* fix(i18n): truncate long checklist labels with tooltip in get-started sidebar
Long translations (e.g. Japanese) were wrapping to two lines in the narrow
sidebar. Now each label truncates with ellipsis and shows the full text in a
tooltip on hover. Icons also get shrink-0 to stay fixed-size.
* [autofix.ci] apply automated fixes
* fix(i18n): align assistant banner translation with panel title in ja, es, pt
GP inconsistently kept "Assistant" in English in the banner string while
translating it natively in the panel title. Patched directly:
- ja: "Assistant" → "アシスタント"
- es: "Assistant" → "Asistente"
- pt: "Assistant" → "Assistente"
fr, de, zh-Hans were already consistent.
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix: prevent X-Forwarded-For bypass of login rate limit
* fix: update drift-guard test to include forwarded_allow_ips key
---------
Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local>
The LFX 0.5.0 -> 1.10.0 realignment (#13176) left every src/bundles/*
package and the port_bundle.py generator flooring lfx>=0.5.0. That
silently permits resolving against the now-dead 0.5.x line: a bundle
built against 1.10.0's BUNDLE_API would ImportError there, and the
BUNDLE_API_VERSION check (both "1") would not catch it. RELEASE.md flags
exactly this — the jump "affects downstream pins, and neither pip nor uv
will flag it."
- Bump the 4 bundle pyprojects (arxiv, docling, duckduckgo, ibm) to
"lfx>=1.10.0,<2.0.0": floored at the current major.minor line, capped
below the next lfx major. Fine-grained API compat stays enforced via
extension.json's lfx.compat against BUNDLE_API_VERSION, not the cap.
- Add scripts/ci/sync_bundle_lfx_pin.py and call it from `make patch` so
future releases keep bundle floors in step. Idempotent: a no-op on
patch releases, moves the floor on minor/major bumps. Leaves docling
self-refs and the nightly lfx-nightly== form untouched.
- port_bundle.py now derives the floor from src/lfx/pyproject.toml, so
newly-ported bundles are born at the current line.
- Update src/bundles/PORTING.md and add test_bundle_lfx_pin.py (20 tests).
* feat(lfx): synchronize LFX onto Langflow major.minor version line (Phase 1)
- Bump src/lfx/pyproject.toml from 0.5.0 to 1.10.0
- Tighten langflow-base lfx pin: ~=0.5.0 → ~=1.10.0
- Fix release.yml pre-release boundary from next-major to next-minor (<X.(Y+1).dev0)
- Add minor-parity soft check to scripts/release-lfx.sh
- Extend make patch to sync lfx version alongside langflow/base/frontend
- Document LFX compatibility contract in RELEASE.md
Contract: LFX X.Y.N is compatible with any Flow from Langflow X.Y.M.
* feat(lfx/upgrade): add compatibility checker (Phase 2)
* fix(lfx/upgrade): fix registry_code guard and types order-sensitivity in checker
* feat(lfx/upgrade): implement safe-upgrade applier (Phase 2)
* feat(lfx): add lfx upgrade command (Phase 2)
* feat(lfx/run): add --upgrade-flow option (Phase 2)
* feat(lfx/serve): add --upgrade-flow option (Phase 2)
* update lfx pyproject version
* [autofix.ci] apply automated fixes
* test(lfx/upgrade): add v1.9.0 starter flow fixtures for upgrade integration tests
* test(lfx/upgrade): add v1.9.0 starter flow fixtures and real-flow integration tests
* [autofix.ci] apply automated fixes
* fix(lfx/upgrade): use alias-aware registry lookup so renamed components are not falsely blocked
* fix(lfx/upgrade): handle outer flow envelope and file-path inputs in upgrade checks
* fix(lfx/upgrade): fail-fast on upgrade_flow errors; add regression tests for Bugs 1-3
* chore(tests): remove bug-number labels from test comments
* [autofix.ci] apply automated fixes
* fix(lfx/upgrade): address PR review comments
- Reject .py files early in serve --upgrade-flow with a clear error message
- Preserve outer flow envelope metadata (name, description, etc.) when lfx upgrade --write rewrites a file
- Move Mapping import under TYPE_CHECKING to satisfy Ruff TC003
- Assert fixture flows and registry are non-empty so parametrized tests cannot pass vacuously
- Remove unused capsys arg and replace print with sys.stderr.write (Ruff T201/ARG001)
- Add upgrade_flow param to run command docstring
* fix(lfx/upgrade): preserve envelope on run, apply nested upgrades (#13200)
* fix(lfx/upgrade): preserve envelope on run, apply nested upgrades
Three follow-ups on top of the upgrade tooling:
- run --upgrade-flow: re-attach the inner graph to the outer envelope before handing the flow to aload_flow_from_json. Previously the upgrade path unwrapped {"data": ...} and passed the inner dict to the loader, which raised KeyError: 'data'. Adds happy-path tests for envelope and flat file inputs.
- applier: recurse into one level of nested flow nodes (node.data.node.flow.data.nodes), matching the checker. Without this, outdated_safe nodes inside grouped components were reported but never written. Adds a regression test.
- upgrade --write test: assert the envelope is preserved (name, description, endpoint_name, etc.) instead of the previous unwrapped-output expectation, so the test matches the actual fix in this PR.
- Drive-by ruff cleanup: D417 docstring, SIM103, SIM108, E501, RUF059.
* [autofix.ci] apply automated fixes
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix(lfx/upgrade): fix loader KeyError, outer-envelope checker gap, and nested-node applier
Three bugs from ogabrielluiz's PR review:
1. run/base.py loader KeyError: file-path branch unwrapped the outer envelope
for the checker but passed the inner dict to aload_flow_from_json, which does
flow_graph["data"] unconditionally. Re-wrap as {"data": flow_dict} before the
loader call. Applied consistently to all three input paths (--flow-json,
--stdin, file-path).
2. run/base.py zero-node checker for --flow-json/--stdin: those paths stored the
raw parsed JSON without unwrapping, so a caller passing an exported flow
{"name":..., "data":{...}} caused the checker to see zero nodes and silently
pass. Now unwrap with raw.get("data", raw) matching the file-path branch.
3. applier.py nested flows never upgraded: the early `continue` on top-level
nodes not in safe_ids prevented the nested-node loop from running. Restructured
so the nested check fires unconditionally for every top-level node, mirroring
checker.py:160-165.
* fix(lfx/run): unify outer-envelope unwrap across all --upgrade-flow input paths
File-path reads now call raw.get("data", raw) matching --flow-json and
--stdin, so the upgrade checker always sees the inner graph. This also
removes the has_envelope/re-wrap machinery that was double-wrapping
outer-envelope files when passing to aload_flow_from_json.
Fix two test assertions that described the old double-wrapped shape.
* fix(lfx/upgrade): inject registry into upgrade_command; fix Makefile lfx pin regex
- upgrade_command now accepts an optional registry parameter so tests
can pass the dict directly instead of mocking load_registry_from_index
- Makefile sed regex broadened from \"lfx~=.*\" to match both ~= and >=
forms so make patch works after release.yml rewrites the pin
- Echo label changed to LFX (synced) to clarify the variable is the
shared Langflow version, not a separate LFX-specific value
* fix(make/patch): fix langflow-base sed regex and validation grep
The dependency in pyproject.toml is "langflow-base[complete]>=X.Y.Z",
not the "langflow-base==X" form the original regex expected, so make
patch silently left the pin unchanged and the validation step always
failed. The sed pattern now matches any extras/operator combination and
rewrites to the canonical [complete]>= form; the grep uses -F so the
[complete] brackets are treated literally.
* test(lfx): add patch regex tests and symmetric safe-mode envelope tests
test_patch_regexes.py — 15 tests covering the Python regexes embedded in
the Makefile patch target. Exercises all three substitutions (langflow-base
pin, lfx pin, version field) against every realistic pin format including
the >=X.Y.Z,<dev0 form that release.yml writes. Would have caught the
langflow-base==.* vs [complete]>= mismatch before manual testing.
test_base.py — two new TestUpgradeFlowOption tests:
test_upgrade_flow_safe_envelope_inline_json_loads_successfully
test_upgrade_flow_safe_envelope_stdin_loads_successfully
Symmetric to the existing file-path envelope test; verifies that --flow-json
and --stdin with an outer-envelope flow also pass {"data": inner} to the
loader after safe upgrades, not a double-wrapped {"data": outer_envelope}.
* fix(lfx/upgrade): address review: compat checker, shared gate, fail-fast registry
Checker correctness:
- _outputs_are_compatible: drop cosmetic display_name from the breaking check;
treat widened output types as safe (flow types must be a subset of registry
types), only narrowing breaks downstream edges.
- _input_types_contained: stop flagging widened input_types as breaking; keep
narrowing as the only breaking case; fix misleading comments.
- check_flow_compatibility now recurses fully into nested grouped components
(symmetric with the applier) and accepts a pre-built registry lookup.
CLI/run/serve:
- New lfx.upgrade.cli_gate (UpgradeFlowMode enum, UpgradeFlowError,
apply_upgrade_gate) shared by run_flow and serve_command so the two
--upgrade-flow paths can't diverge.
- run_flow: extract _materialize_flow_dict and route gating through the shared
helper.
- run/serve --upgrade-flow options typed as UpgradeFlowMode (check|safe choices).
- lfx upgrade: load_registry_from_index fails fast when the bundled registry is
empty/missing instead of silently marking every node blocked; ASCII-only report
output; new --strict flag; build the registry lookup once and reuse it.
Docs/tests:
- RELEASE.md: migration note for the lfx 0.5.0 -> 1.10.0 version jump.
- Regression tests for the checker fixes, the shared gate, fail-fast registry,
--strict, and serve --upgrade-flow parity.
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* fix(lfx): consolidate flow-envelope handling and fix version-ceiling regex
Extract the outer-envelope unwrap/rewrap logic (previously hand-rolled as
raw.get("data", raw) with subtly different rules across serve, run, and
upgrade) into a single lfx.utils.flow_envelope module with split/merge
helpers. This fixes a serve bug where an enveloped flow had its inner graph
written bare to the temp file, making the loader's flow_graph["data"] raise
KeyError.
Also fix release.yml's major.minor extraction: the greedy sed grabbed the
upper bound from a range pin (>=1.10.0,<1.11.dev0), drifting the version
ceiling up one minor each release cycle. Anchor to the first version instead.
* fix(lfx): upgrade-flow gate reads bundled component index, not empty cache
The --upgrade-flow=check|safe gate on lfx run and lfx serve rejected every
flow as 'blocked'. Both call sites passed component_cache.all_types_dict to
apply_upgrade_gate, but that cache is populated lazily after services start,
so at gate time it is empty -- an empty registry classifies every node as
blocked. The standalone lfx upgrade command was unaffected because it reads
the bundled _assets/component_index.json instead.
Make the gate own registry loading: apply_upgrade_gate now defaults
all_types_dict to None and loads the bundled index (the same source lfx
upgrade uses) via a new _load_bundled_registry helper, raising
UpgradeFlowError on a missing/empty index so a broken install fails loudly
instead of silently blocking every component. Both call sites pass mode=
and let the gate load the registry.
Existing gate tests mocked component_cache with a populated registry, which
is exactly what hid the bug; repoint them at the new _load_bundled_registry
seam and add regression tests that do not mock the registry, including an
end-to-end run_flow check against a real clean v1.9.0 starter flow.
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@logspace.ai>
DB2VectorStoreComponent.search_documents() returned early when no search query was provided, before calling build_vector_store() — which is what ingests ingest_data. As a result, ingestion only ran when a search query was also supplied.
Build (and ingest into) the vector store first, then skip only the search when no query is present, matching LCVectorStoreComponent. Adds a regression test asserting ingestion runs with no query.
* fix: enable agent-lifecycle-toolkit (altk) on Python 3.14
agent-lifecycle-toolkit 0.10.x dropped its own requires-python <3.14
cap (now >=3.10) and does not depend on OpenDsStar, so the
`python_version < '3.14'` gate on the altk extra is stale.
- Remove the python_version < '3.14' marker from the altk extra in
langflow-base, keeping the macOS x86_64 platform exclusion.
- Regenerate uv.lock: agent-lifecycle-toolkit resolves on 3.14 and
coexists with the onnxruntime>=1.26 langflow forces there
(verified agent-lifecycle-toolkit 0.10.1 + onnxruntime 1.26.0).
OpenDsStar (the separate OpenDsStarAgent component) stays gated: all
its releases still cap requires-python at <3.14 upstream.
* test: correct stale <3.14 comment in ALTK test import guards
The module-level import skips in the ALTK agent tests carried an
outdated comment claiming "agent-lifecycle-toolkit is gated to
python_version<'3.14' upstream". That is no longer accurate:
agent-lifecycle-toolkit 0.10.1 dropped the <3.14 cap (now requires
>=3.10), and this PR removes the langflow <3.14 marker on the altk extra.
The skip itself is preserved -- altk is an optional extra
(langflow-base[altk]) that may be absent from a given environment, so
the ImportError guard is still required. Only the comment is corrected,
to state the real reason for the skip plus the upstream change. Applied
to all four files carrying the identical comment (test_altk_agent,
test_altk_agent_logic, test_altk_agent_tool_conversion,
test_conversation_context_ordering).
Also tag a pre-existing fake test key (api_key="sk-test") with
`# pragma: allowlist secret`; the comment shift re-staged the file and
surfaced it to detect-secrets.
Validated (Python 3.13, altk installed): ruff clean; the three logic
modules run (64 passed, 4 skipped) and test_altk_agent collects (13).
* fix: enable IBM watsonx.ai bundle on Python 3.14
ibm-watsonx-ai (1.5.13) and langchain-ibm (1.1.0) added Python 3.14
support upstream on 2026-06-03, so the official 3.14 Docker image no
longer needs to silently drop the IBM integration.
- Lift the `python_version < '3.14'` markers on ibm-watsonx-ai and
langchain-ibm in both the lfx-ibm bundle and langflow-base.
- Bump langchain-ibm from `~=1.0.2` to `~=1.1.0`; the 3.14-capable
release is 1.1.0, which the old `~=1.0.2` pin excluded.
- Regenerate uv.lock: the resolver now forks ibm-watsonx-ai into
1.5.13 for py>=3.11 (incl. 3.14) and 1.3.42 for py<3.11, so 3.10
keeps working while 3.14 gains the integration.
watsonx-orchestrate (ibm-watsonx-orchestrate-core/clients) stays gated
at <3.14 since upstream 2.10.0 still caps there.
* test: stop import-skipping IBM watsonx tests on Python 3.14
The watsonx test modules skipped at module load (pytest.skip with
allow_module_level=True) when langchain-ibm / ibm-watsonx-ai failed to
import, with comments tying the skip to the upstream <3.14 cap. Now that
those deps are importable on 3.14 (this PR's dep bump), the import-based
skip can hide real import regressions instead of surfacing them.
- test_model_utils.py, test_watsonx.py, test_watsonx_embeddings.py:
replace the try/except pytest.skip(allow_module_level=True) guards with
direct top-level imports so a missing or broken IBM SDK fails loudly.
Drop the now-unused `import pytest` in test_model_utils.py.
- src/bundles/ibm/README.md: update the platform note to reflect that the
watsonx components are now importable on Python 3.10-3.14.
The Db2 tests (test_db2*, test_optional_dependency) are left untouched --
they gate on ibm-db's linux/aarch64 platform exclusion, not the Python
version, which is still valid.
Validated locally (Python 3.13): 63 tests pass (13 + 50), ruff clean.
* feat(security): add failed login logging with IP tracking and comprehensive tests
* fix: remove PII from login logs and fix structlog format
* docs: agent improvements (#13269)
* docs: add structured output for agents
* docs: add structured output note
* docs: clarify both agent outputs and add release notes
* docs: when langflow sends events to the playground and chat history
* fix(playground): expand session checkbox click target to full row height (#13349)
* fix(playground): expand session checkbox click target to full row height
The selectable-row checkbox wrapper was ``w-4 h-4`` (16x16 px) but the
row is ``h-8`` (32 px). The 8 px band above and the 8 px band below
the visible icon were dead zones that bubbled to the row's
``toggleVisibility`` and navigated to the session instead of toggling
selection — exactly the accidental-navigation UX the bug ticket
describes.
Grow the wrapper to ``w-4 h-8`` so the click target captures the full
row height. Width stays narrow so the text alignment is unchanged; the
icon itself stays ``h-4 w-4``, centered via the existing
``flex items-center justify-center``. Visual position is identical.
Adds ``__tests__/session-selector-checkbox-click-target.test.tsx``
(3 cases): wrapper className guard (h-8 + w-4, not h-4), click on the
wrapper outside the icon triggers selection without bubbling to
toggleVisibility, and click on the inner icon still toggles selection.
* test(playground): drop checkbox-click-target test file (not needed per review)
* [autofix.ci] apply automated fixes
* test: align session-selector checkbox assertions with full-height click target
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix: improve trace search functionality (#13383)
* improve trace search functionality
* [autofix.ci] apply automated fixes
---------
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* test: fix test_login_logs_real_output_format to use mocking
- Updated test to use patch() instead of capsys for consistency
- Verifies structlog kwargs format (not nested extra dict)
- All 7 login logging tests now passing
* feat: Add IP-based rate limiting to login endpoint
- Add slowapi dependency for rate limiting functionality
- Implement rate limiting service with configurable limits
- Apply rate limiter to /login endpoint (default: 5 attempts/minute per IP)
- Add custom exception handler for rate limit exceeded (HTTP 429)
- Support both in-memory (default) and Redis storage backends
- Add comprehensive test suite with 100% coverage
- Document configuration in .env.example
Security improvements:
- Prevents brute force attacks on login endpoint
- Structured logging without PII (logs client_ip, not user data)
- Graceful degradation with swallow_errors=True
- Proper X-Forwarded-For header handling for proxied requests
Configuration:
- LANGFLOW_RATE_LIMIT_PER_MINUTE: requests per minute (default: 5)
- LANGFLOW_RATE_LIMIT_STORAGE: storage backend (default: memory://)
- LANGFLOW_RATE_LIMIT_HEADERS_ENABLED: enable rate limit headers (default: false)
* security: add proxy-aware IP extraction for rate limiting
- Add LANGFLOW_RATE_LIMIT_TRUST_PROXY configuration
- Default to secure get_remote_address (prevents header spoofing)
- Enable get_client_ip when behind trusted proxies
- Add comprehensive tests for both configurations
- Maintain 100% test coverage (15 tests passing)
* refactor(rate-limit): address PR review feedback - migrate to Settings model, fix IP security, add test coverage
* fix: disable rate limiting in tests to prevent 429 errors
- Add LANGFLOW_RATE_LIMIT_ENABLED environment variable control
- Disable rate limiting by default in tests via session-scoped fixture
- Rate limit tests explicitly enable it via function-scoped fixture
- Fixes test failures caused by sequential login attempts hitting 5/min limit
* fix: refactor login rate limiting to avoid settings initialization race condition
---------
Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local>
Co-authored-by: Mendon Kissling <59585235+mendonk@users.noreply.github.com>
Co-authored-by: keval shah <kevalvirat@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: olayinkaadelakun <olayinka.adelakun@ibm.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>