The loop body now runs its subgraph through get_default_coordinator().stream,
so InProcessExecutor calls async_start with inputs/max_iterations/config/
reset_output_values/fallback_to_env_vars alongside event_manager. The mock
only accepted event_manager and raised TypeError on the extra kwargs. Widen it
to **_kwargs while still capturing event_manager for the assertion.
teardown() emptied self.factories but left factory_registered set, so
get_service()'s lazy re-registration was skipped and later lookups raised
NoFactoryRegisteredError. Once Graph.arun started routing through the
executor service, that surfaced as intermittent flow-execution 500s in the
backend unit suite whenever a sibling test had torn the global manager down.
- ExecutorService discovery skips a loaded entry-point object that is not an
Executor instance instead of registering it and failing only at execute() time
- Coordinator.run docstring notes the single-Unit / single-RunComplete assumption
that identity_partition currently guarantees
- correct two stale docstrings: Unit's session_id is honored only on the legacy
passthrough path, and ExecutorService.teardown no longer claims the service
manager keeps the cached instance
- register ExecutorServiceFactory in register_all_service_factories so
EXECUTOR_SERVICE resolves at app startup; without it the seam raised
NoFactoryRegisteredError across integration and mcp tests
- test_base.py graph.async_start mocks now default the leading positional
param (the seam calls async_start with inputs as a keyword), fixing the
'missing 1 required positional argument' failures
- add executor_kind to the settings composition EXPECTED_FIELDS set
- run_to_completion and Coordinator.stream wrap iteration in contextlib.aclosing
so the underlying generator is finalized when the loop returns or raises early
- execute_loop_body wraps its stream in aclosing for the same mid-iteration guarantee
- drop the unused inputs param from Coordinator.stream; the streaming path reads
initial_inputs from runtime_options, so inputs was a silent no-op, and document it
- test_coordinator_uses_settings_executor_kind uses monkeypatch + a finalizer so a
mid-test failure can't poison session settings
- test_registry_returns_same_executor_instance_across_runs builds a fresh graph per run
- test_set_default_coordinator_overrides_singleton restores the singleton afterward
Robustness pass on the execution seam after using it to integrate an external
executor end-to-end. Four things this pins down:
1. Reusable executor contract suite
- tests/unit/execution/test_executor_contract.py
- ExecutorContract with 7 universal seam guarantees: kind shape, execute()
returns an AsyncIterator, stream ends with exactly one RunComplete,
instance is reusable across runs, concurrent runs are isolated, consumer
cancellation does not hang or leak, execute() signature is stable.
- TestInProcessExecutorContract subclasses and provides fixtures. Downstream
executors (stepflow, future remote/sandbox) get the same battery by
subclassing and overriding two fixtures.
2. Cancellation/cleanup correctness
- tests/unit/execution/test_cancellation.py
- InProcessExecutor wraps graph.async_start() in try/finally with explicit
aclose so consumer aclose cascades to the underlying graph generator.
Previously the inner generator was abandoned and only finalized on a GC
pass: a real leak for executors holding subprocesses or sockets.
- Coordinator.run and Coordinator.stream propagate aclose the same way so
the full chain (consumer -> coordinator -> executor -> graph) cleans up
deterministically.
3. Documented seam contracts
- Unit.runtime_options: free-form bag, "_"-prefixed keys reserved for
executor-internal flags, common in-process keys listed.
- StepResult.payload: untyped on purpose; each executor defines its own
event vocabulary; consumers normalize at consumption site.
- RunComplete.outputs: only the legacy in-process path populates it;
streaming consumers should collect from StepResult.payload.
- Executor ABC: lifecycle (shared instance, must be reusable, must tolerate
concurrent execute() calls and consumer aclose).
4. Coordinator-routed E2E suite
- tests/unit/execution/test_coordinator_e2e.py
- Real Graph through Coordinator -> registry -> InProcessExecutor chain.
- Asserts payload shape, RunComplete never leaks to stream() consumers,
dispatch routes to configured kind, registry returns same instance.
Full execution suite: 63 passing. flow_executor coordinator test: passing.
Introduce an ExecutorService that owns the registry and the default coordinator,
discovered through the standard lfx ServiceManager (parity with cache, database,
storage, etc). Add an executor_kind setting so the default kind can be configured
without touching code, and an lfx.executors entry-point group for third-party
executors. Entry-point discovery refuses to overwrite an existing kind so an
installed package cannot silently replace the built-in in-process executor;
explicit replacement still works through ExecutorService.register().
The lfx.execution public API (get_default_coordinator, get_default_registry,
set_default_coordinator, reset_default_coordinator) now resolves through the
service manager but keeps the same surface, so existing call sites in Graph.arun,
flow_executor, the CLI, run/base, and loop_utils are unchanged.
Also surface root-cause tracebacks in lfx.services.deps.get_service: the helper
still returns None on failure (callers like get_db_service rely on that), but it
now logs the exception so init failures stop disappearing into the void.
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: add Firecrawl Search API to the bundle page
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* fix(firecrawl): D205 docstring + defensive .get('data') in crawl
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(firecrawl): D205 docstring in scrape
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(firecrawl): use list.extend in map (PERF401)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: add unit tests for Firecrawl v2 components
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* test: align Firecrawl component tests with lfx _attributes pattern
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* refactor(firecrawl): remove deprecated extract endpoint component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(firecrawl): remove deprecated extract endpoint component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: drop extract component test (extract endpoint removed)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: remove Firecrawl Extract API section (deprecated)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* fix(firecrawl): map Search component to Firecrawl icon, drop Extract
The frontend icon map in styleUtils.ts still referenced the removed
FirecrawlExtractApi and lacked the new FirecrawlSearchApi, so the Search
node would not render the Firecrawl logo.
* chore: regenerate component index for Firecrawl v2 (Search added, Extract removed)
* refactor(firecrawl): extract components into the lfx-firecrawl extension bundle
Port the firecrawl provider out of lfx.components into a standalone
Extension Bundle at src/bundles/firecrawl (distribution: lfx-firecrawl),
following src/bundles/PORTING.md:
- Move the v2-SDK components (Scrape, Crawl, Map, Search) to
src/bundles/firecrawl/src/lfx_firecrawl/components/firecrawl/ and
remove the in-tree provider + its lfx.components registration.
- Own the firecrawl-py>=4,<5 pin in the bundle; drop it from
langflow-base.
- Wire the workspace (root pyproject deps/sources/members) and uv.lock.
- Append migration-table entries (bare name, both import paths, pre-a
slot) for the four classes; FirecrawlExtractApi gets no entries since
the v2 SDK removed the extract endpoint and the component was dropped.
- Regenerate the component index (firecrawl category removed) and
locales/en.json (54 firecrawl keys move out of core).
- Bundle-local unit tests + test_pilot_firecrawl_upgrade integration
test; update Dockerfile bundle enumeration comments.
* fix(lfx): repair migration table entry fused during release-1.11.0 merge
The release-1.11.0 back-merge collided the FirecrawlSearchApi legacy_slot
entry with the NextPlaidVectorStoreComponent bare_class_name entry, mashing
both into a single object with duplicate 'target' keys, populating two of
{bare_class_name, import_path, legacy_slot}. That fails the MigrationTable
validator (entries.51) and broke the lfx loader tests.
Split it into the two intended entries so each populates exactly one
key-field. Restores the FirecrawlSearchApi and NextPlaidVectorStoreComponent
quads (60 entries total, +16 vs base). Append-only and bare-name guards pass.
---------
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>
Co-authored-by: Eric Hare <ericrhare@gmail.com>
* fix(authz): schedule periodic audit-log retention cleanup
clean_authz_audit_log() was only invoked once, at startup inside
initialize_services(). A long-running instance never pruned authz_audit_log
again after boot, so the table grew unbounded between restarts even though the
retention helper was already implemented and unit-tested.
Wire the same helper to a recurring background worker:
- New AuditLogCleanupWorker (services/task/audit_cleanup.py), modelled on the
sibling temp_flow_cleanup.CleanupWorker: a stop-event-driven asyncio task that
prunes on a fixed interval in its own session_scope(). Best-effort -- the
helper logs-and-swallows DB errors and an outer guard keeps the loop alive, so
it never blocks the event loop or request path.
- Gated: the worker only schedules when AUTHZ_AUDIT_ENABLED is True and
AUTHZ_AUDIT_RETENTION_DAYS > 0; otherwise start() is a no-op. Retention=0
stays a no-op end to end.
- Sleep-first loop: the unconditional startup sweep still prunes at boot, so the
first scheduled pass waits one interval to avoid a redundant immediate delete.
The startup sweep is left unchanged.
- New AUTHZ_AUDIT_CLEANUP_INTERVAL setting (default 86400 = daily, floor 300s).
- Started/stopped from the application lifespan, both best-effort.
Tests (test_audit_cleanup_worker.py) show cleanup runs repeatedly on the
recurring schedule (not just at startup), is a no-op when retention or auditing
is disabled, survives sweep failures, and -- end to end against a real SQLite
engine -- prunes a row inserted after startup while leaving in-window rows.
Closes the audit-retention-scheduling gap (C) for OSS RBAC on release-1.10.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(authz): read AUTHZ_AUDIT_CLEANUP_INTERVAL directly
The cleanup worker resolved the sweep interval via getattr-with-default
plus a TypeError/ValueError guard, mirroring the field default in a
module constant. Pydantic already guarantees the field is present and
>= 300, so those fallbacks were unreachable and the constant was a
second source of truth that could drift.
Read the setting directly; keep DEFAULT_CLEANUP_INTERVAL_SECONDS only as
the pre-start() placeholder for unstarted workers. Drop the matching
missing/garbage-fallback test assertion.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(authz): RBAC enforcement + share-lifecycle integration tests via a test-double enforcer
No automated test exercised end-to-end authorization enforcement. The OSS
authorization service is a pass-through (enforce() always returns True,
supports_cross_user_fetch() is False), so allow/deny semantics could not be
asserted against it — only guard-wiring unit tests existed.
Add a lightweight, OSS-only test-double enforcer plus integration tests that
drive the real flow routes over HTTP under a genuine allow/deny signal — no EE
Casbin package required.
- _policy_double.py: PolicyTestAuthorizationService derives allow/deny from the
seeded authz_role / authz_role_assignment / authz_share rows and sets
SUPPORTS_CROSS_USER_FETCH=True. install_policy_authz() swaps it onto the
service manager (str-enum keys make ServiceType interchangeable) and flips
AUTHZ_ENABLED=true / AUTHZ_SUPERUSER_BYPASS=false, restoring both on exit, so
every get_authorization_service() call site (guards, fetch, listing, helpers)
sees it for the duration of a test. Ships seed helpers (roles/assignments/
shares) so tests stay declarative.
- test_rbac_enforcement_integration.py:
* Role matrix (Phase 1.11) on flow routes, with flows owned by a separate user
so the guards' owner-override does not mask the role decision:
- viewer: read + execute (build) allowed; write/delete -> 404; create -> 403
- developer: write + create allowed; delete -> 404
- admin: full, including delete
* Share lifecycle (Phase 3.13): Alice shares a flow with Bob ->
Bob GET/PATCH/build succeed; without the share every fetch route -> 404
(UUID-privacy mask). A read-level share grants GET but 404s PATCH, proving
permission_level (not mere share presence) is enforced.
* Domain resolution: a workspace-scoped grant authorizes a flow in that
workspace but 404s a flow in another — trips if _resolve_authz_domain
regresses.
In each test a cross-user GET -> 200 (only possible with enforcement on +
share-aware fetch) is the linchpin proving the paired deny is real enforcement,
not an owner-scoped fetch miss; removing a guard flips a 404 to 200/200.
Closes the enforcement integration-test gap (D) for OSS RBAC on release-1.10.0.
Per the ticket's triage note, the reusable test-double also stands alone if the
team prefers the deny-path matrix to live in EE.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(authz): harden policy double per review — strict scoped grants, deterministic seeding, read-share build denial
- _assignment_covers: a scoped assignment missing domain_id no longer
widens to global coverage; only domain_type='global' is global
- assign_role: fail fast (ValueError) on non-global assignments without
a domain_id so malformed grants can't hide domain-resolution bugs
- seed_system_roles: overwrite preexisting viewer/developer/admin rows
so the asserted policy matrix is always SYSTEM_ROLE_PERMISSIONS
- read-only share test: also assert build/execute is denied (404), not
just PATCH
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* nextplaid integrate
* doc ids fix
* multivec fixes
* type check
* refactor: ship NextPlaid as the lfx-nextplaid extension bundle
Convert the in-tree NextPlaid vector store and its companion vLLM
multivector embeddings into a standalone `lfx-nextplaid` Extension Bundle,
following src/bundles/PORTING.md (cf. lfx-arxiv, lfx-ibm). The whole
feature now ships as an additive bundle with no core modifications.
- Move both components into src/bundles/nextplaid (components/nextplaid),
dropping src/lfx/.../components/nextplaid and reverting the core vllm
__init__ multivector additions.
- Bundle declares its own runtime deps (langchain-plaid, pillow) and ships
extension.json + the langflow.extensions entry point.
- Add migration_table entries mapping the legacy class names / import
paths to ext:nextplaid:*@official; wire the workspace (root pyproject,
uv.lock).
- Add the pilot upgrade integration test and bundle-local unit tests.
- Declare explicit outputs on NextPlaidVectorStoreComponent so the
extension validator can resolve its output methods.
* fix: address CodeRabbit review on NextPlaid bundle
- nextplaid: derive stable text IDs from source/page+content (not content
alone) and fingerprint raw PIL images by bytes (not batch position) so
ingests upsert instead of colliding/overwriting unrelated vectors
- vllm impl: validate the /pooling response envelope before nested indexing,
raising a clear RuntimeError on malformed text/image responses
- icon: use the isDark boolean prop contract instead of the isdark string
- test: gate the distribution check on package presence (PackageNotFoundError)
so genuine import failures surface instead of being skipped
---------
Co-authored-by: Meet <meet@dhcp-9-127-22-227.c4p-in.ibm.com>
Co-authored-by: Eric Hare <ericrhare@gmail.com>
fix(frontend): restore Inspection Panel access to hidden advanced fields
Fields that were both advanced=True and listed in HIDDEN_FIELDS became
unreachable from the Inspection Panel: hidden from the node body (advanced)
and removed from the panel (HIDDEN_FIELDS), leaving no edit path. This
affected every HIDDEN_FIELDS entry, originally reported for Chat Input/Output
Store Messages (should_store_message).
Keep HIDDEN_FIELDS out of the default advanced view (the #12473 declutter
intent) but stop filtering them out of edit-fields mode, so users can surface
and edit them via the visibility toggle. Also drop the stale Agent verbose
entry (removed from the component via drop = {verbose}) while keeping the
still-live format_instructions and output_schema hidden. i18n the two
remaining hardcoded panel strings.
Fixes#13595
fix(frontend): remove duplicate tooltip and raw i18n key on collapsed sidebar nav
The floating canvas control nav (shown when the sidebar is collapsed)
rendered two tooltips per icon: a native `title` attribute on
ControlButton plus the styled ShadTooltip. On top of that,
MemoizedComponents passed the raw i18n key (`item.tooltip`) as the
tooltip text instead of running it through `t()`, so the ShadTooltip
exposed strings like `sidebar.nav.bundles` while the native title showed
the bare id (`bundles`).
- CanvasControlButton: drop the native `title` (the duplicate tooltip);
keep accessibility via `aria-label`. The ShadTooltip is the single
visual tooltip. This also removes the redundant native tooltip from the
zoom/fit/lock control buttons.
- MemoizedComponents: translate the nav tooltip with `t(item.tooltip)`.
Adds an isolated CanvasControlButton regression test asserting no native
`title` and that the label is surfaced via aria-label + ShadTooltip.
* fix: fail fast when Redis job queue backend is unreachable
When LANGFLOW_JOB_QUEUE_TYPE=redis is set but Redis is not reachable,
Langflow booted normally and then raised a raw redis ConnectionError as
a 500 on the first flow execution, with no clear cause.
Probe Redis at startup (bounded retry) and abort boot with an actionable
error when it stays unreachable, mirroring the existing external-cache
connectivity check. Translate a later Redis outage on the build and
ownership paths into a clean HTTP 503 via a typed
JobQueueBackendUnavailableError instead of a raw stack trace.
* [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]
* [autofix.ci] apply automated fixes
* fix: address review - probe redis pre-start, redact credentials, cancel orphaned build
- is_connected() now probes with a temporary client when the service has
not started yet: the startup fail-fast in initialize_services() runs
before the per-worker start() creates the client, so it previously
rejected every redis boot, healthy or not
- connection_target redacts URL userinfo so credentials never reach
server logs or the HTTP 503 detail
- build_flow cancels the just-started build (best-effort) when owner
registration fails, instead of leaving an unreachable build running
- register_job_owner only records the local owner after the Redis write
succeeds, so a failed write cannot leave same-worker ownership checks
passing while other workers see the job as unowned
---------
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>
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.
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.
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.
The first 1.11.0.dev0 nightly failed its "Test Langflow Main CLI" step:
the fork bump's sync_bundle_lfx_pin.py re-synced every bundle floor to
lfx>=1.11.0, and PEP 440 sorts the nightly's 1.11.0.dev0 BELOW 1.11.0,
so the workspace-built bundles (whose metadata shadows the satisfiable
PyPI lfx-arxiv 0.1.1 during `uv pip install dist/*.whl`) reject the
branch's own lfx while langflow-base pins it exactly — unresolvable.
Floor at lfx>=X.Y.0.dev0,<(X+1).0.0 instead: X.Y.0.dev0 is the lowest
version PEP 440 admits in the minor line, so every devN / rcN / final
satisfies the floor while older lines and the next major stay excluded.
This closes the NIGHTLY.md activation-gate hole structurally — future
minor forks re-sync to a floor their own nightlies already satisfy.
- sync_bundle_lfx_pin.py: lfx_floor_spec emits the .dev0 floor
- port_bundle.py: mirrored _current_lfx_floor kept in step
- bundle pyprojects restamped via the script (arxiv/docling/duckduckgo/ibm)
- test_bundle_lfx_pin.py expectations updated (20/20 passing)
- NIGHTLY.md gate section annotated with the post-activation fix
uv.lock is unaffected (workspace lfx is recorded as an editable source
with no specifier — which is also why uv sync/lock passed in the same
job). The release.yml RC floor-relax sed still matches the new form;
now redundant but harmless. No bundle version bump needed: published
0.1.1 floors >=1.10.0.rc0, satisfiable by the whole 1.11 line.
The release-1.11.0 fork bumped lfx to 1.11.0 but left component_index.json's version field at 1.10.0. _read_component_index fails closed on exact version mismatch, so the bundled registry loads as None and the upgrade-gate tests fail (UpgradeFlowError: 'bundled component registry is empty or missing') across the LFX test matrix. That failure blocks the nightly's release-nightly-build job, so no canonical 1.11.0.devN is published and check-nightly-status blocks CI everywhere.
Surgical restamp: version -> 1.11.0 and recomputed sha256 (same orjson OPT_SORT_KEYS hashing and OPT_SORT_KEYS|OPT_INDENT_2 serialization as scripts/build_component_index.py). Entries unchanged. Verified the reader validation passes and the three failing tests go green.
* 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>