stop() cancelled in-flight tasks but returned without awaiting them, so a job's
shielded terminal reconcile (a DB write) could race a closing engine and leave a
'Task was destroyed but it is pending' warning. Cancel and gather the in-flight
job tasks first, then tear down the workers. Cancelling the job tasks directly
(rather than relying on cancellation propagating through the worker's await)
avoids the worker sitting in cancellation limbo behind a job that swallows its
cancel on the user-stop path, which otherwise stalls teardown.
unconsumed_signals filters consumed_at IS NULL but nothing stamped it, so STOP
rows lingered: the table grew and a re-enqueued job self-cancelled off a stale
STOP. Add JobService.consume_signals and stamp the STOP in the runner's
_reconcile_stop, the single point where a stop is finalized.
Two uvicorn workers booting against the same DB both re-enqueued the same QUEUED
row, so a non-idempotent flow executed twice. Add JobService.claim_queued_job, a
conditional UPDATE ... WHERE status='QUEUED' that only one racer can win
(rowcount==1), and gate sweep re-enqueue on it. Works on SQLite and Postgres.
The background_job_timeout setting was read by nobody, so a runaway run never
timed out. Bound the runner's drive with asyncio.wait_for(timeout=...) when the
setting is configured; execute_with_status already maps asyncio.TimeoutError to
TIMED_OUT, so the run ends TIMED_OUT with a terminal event. None keeps the prior
unbounded behaviour. The facade passes settings.background_job_timeout through.
events() decided 'is the job finished' from the process-local live bus, whose
_closed marker is empty after a restart. A reattach to an already-terminal job
replayed durable rows then blocked forever on the live tail. Key the decision off
the persisted job status instead: when terminal, replay the durable log and
return. Re-frame durable rows through the same format_sse_event formatter the
live path uses (with id=str(seq)) so replayed and live frames are byte-compatible
and Last-Event-ID resumes correctly.
The worker used asyncio.Task.cancelling() to tell a job-cancel apart from a
worker-cancel, but that API is Python 3.11+. On 3.10 the first job cancellation
raised AttributeError inside the cancel handler and permanently killed the
worker, hanging later jobs QUEUED. Replace it with an explicit _closed flag set
by stop(): a CancelledError while shutting down re-raises to exit the worker,
otherwise it is a per-job cancel that is swallowed so the pool keeps serving.
Give v2-workflows sync and the langflow stream protocol one parser. The
stream now emits a normalized "output" event per terminal output carrying
an OutputEvent (the ComponentOutput shape sync returns in outputs[id], plus
component_id). A shared build_component_output() backs both the sync
converter and the adapter, and the build loop ships authoritative vertex
metadata as an additive output_meta key on end_vertex (existing consumers
read build_data and ignore it).
This is access-pattern parity (one parser, same fields, same terminal set),
not byte-identical content: the stream reuses the v1 build path whose
display serialization differs from sync's run_graph output.
Let a sync caller name the output(s) they want via output_ids so
output.text resolves deterministically (reason=single) on multi-output
flows instead of going null. Selection is steer-only: it picks the
answer among the named outputs without filtering the outputs map.
Invalid ids are rejected with 422 before the flow runs (and before any
job row is created), so a typo costs no compute. Resolution considers
selected outputs that actually fired, so branching flows resolve to
whichever candidate ran.
Replace the flat output_text shortcut with an `output` object carrying the
resolved text answer plus a `reason` that explains why it resolved that way
(single/multiple/none/non_string/failed), so a null answer is always
diagnosable instead of silently None. `reason` follows the LLM-domain
finish_reason/stop_reason convention, distinct from the lifecycle status.
Also add `display_name` to each ComponentOutput (the stable component id
stays the dict key) and a computed `has_errors` flag derived from errors.
Pin the sync-response shortcuts on the v2 workflows endpoint:
- output_text surfaces the lone ChatOutput/TextOutput text and stays None for
non-output message nodes, data-only flows, and multi-text flows
- session_id echoes the resolved session; the error response exposes neither
- each outputs entry exposes only {type, status, content, metadata}, with the
component id carried by the dict key
Also drop the component_id kwarg the converter passed to ComponentOutput, which
has no such field and silently dropped it.
The synchronous /api/v2/workflows response keyed every result under its
component id, so reading the answer meant knowing an id you can't predict.
Surface two additive fields:
- output_text: the flow's single text answer (ChatOutput/TextOutput). None
when the flow has zero or multiple text outputs, so callers read outputs
rather than the shortcut guessing which channel is the answer.
- session_id: echoes the resolved session so chat/memory callers can
continue the same thread (v1 /run returned this; v2 had dropped it).
outputs is unchanged, so this is non-breaking.
Rebased onto release-1.10.0. The base independently rebuilt the v2
workflows backend (RBAC, body globals, share-aware fetch); keep our
forward design and conform its auth to that work:
1. Auth: keep get_current_user_for_workflow (session-or-API-key authN
that does not hold a DB connection during the inline run, avoiding
the SQLite lock contention api_key_security would cause) and enforce
the base's RBAC on top: ensure_flow_permission(EXECUTE) before run,
(READ) before status reconstruct, with widen_for_shares fetch.
2. Port the base's request-body globals onto the v2 WorkflowRunRequest.
The X-LANGFLOW-GLOBAL-VAR-* headers stay supported (the Responses API
passes globals that way); body globals win on conflict. Converters
echo the effective globals via effective_globals.
3. Public endpoint keeps the v1 build_public_tmp posture
(access_type==PUBLIC, run-as-owner); RBAC applies to the
authenticated endpoint only.
4. Preserve the base's post-build KB-cache invalidation in the AG-UI
build path.
The endpoint, AG-UI bridge, pluggable stream adapters, public endpoint,
and re-attach are unchanged.
* docs: remove 1.8 env vars
* docs: link out to deployment guide
* docs: redis queue
* fix-links-and-combine-env-vars-table
* docs: cleanup
* peer-review
* peer-review
* Apply suggestions from code review
Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com>
* move-prereqs
---------
Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com>
* fix: remove src/backend/base/uv.lock and Dockerfile references
- Deleted src/backend/base/uv.lock (monorepo should have only one uv.lock at root)
- Removed COPY ./src/backend/base/uv.lock lines from all Dockerfiles:
- docker/build_and_push.Dockerfile
- docker/build_and_push_base.Dockerfile
- docker/build_and_push_ep.Dockerfile
- docker/build_and_push_with_extras.Dockerfile
- docker/dev.Dockerfile
Fixes LE-1093
* fix: remove stale base uv.lock regeneration paths
* feat(LE-906): Config/settings plumbing
- Add 7 new boolean settings: custom_component_admin_only, embedded_mode,
hide_logout_button, hide_new_project_button, hide_new_flow_button,
hide_starter_projects, mcp_servers_locked (all default: false)
- All settings configurable via LANGFLOW_* env vars
- Expose all flags through ConfigResponse schema
- Fix hide_getting_started_progress to read from settings not env
- Add frontend type definitions for all 7 flags
- Initialize utility store with all new flags
- Implement hydration logic for new flags in config hook
* [autofix.ci] apply automated fixes (attempt 2/3)
* fix(LE-906): address PR1 review feedback
- replace internal ICA wording with neutral embedded-mode language
- add settings field for hide_getting_started_progress
- map hide_getting_started_progress from settings in ConfigResponse
- add regression test for embedded_mode hide-flag cascade
- revert unrelated component_index drift from PR scope
* fix(LE-906): address remaining PR1 Gabriel comments
* docs/test(LE-906): clarify embedded_mode scope for QA
- Clarify in settings docs that embedded_mode cascades UI hide flags only
- Keep mcp_servers_locked and custom_component_admin_only as explicit opt-ins
- Add config test assertions that security flags do not auto-cascade
* feat(LE-906): Embedded UI visibility changes
- Hide logout button from account menu when hideLogoutButton flag enabled
- Hide new flow button from header when hideNewFlowButton flag enabled
- Hide new project button from sidebar when hideNewProjectButton flag enabled
- Hide starter projects tab from templates modal when hideStarterProjects enabled
- All UI gates read from utility store (flags populated from server config)
- Supports embedded/iframe mode integration where standalone UI elements are hidden
- Pure frontend changes: no backend dependencies, no breaking changes
- Respects embedded_mode umbrella flag when individual hide flags set
* fix(LE-906): restore overwritten header components and keep embedded UI gates
* [autofix.ci] apply automated fixes
* feat(LE-906): hide new flow entry points
* fix(LE-906): address PR4 review comments from Gabriel
* fix(LE-906): resolve PR4 biome lint failures
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* feat(LE-906): Custom component admin gating + fix field refresh side effect
- Add custom_component_admin_only gate to POST /custom_component endpoint
- Only admin users can CREATE new custom component code when flag enabled
- Remove admin gate from POST /custom_component/update endpoint
* This endpoint handles field metadata refresh (e.g., model provider changes)
* Normal users need access to refresh available models/options
* Gate on creation prevents harmful code, not on field refresh operations
- Use hardened feature-flag checks with getattr(..., False) is True pattern
- Fixes side effect where normal users couldn't refresh field metadata
(e.g., couldn't change LLM models when provider config changes)
- Gate applies only to creation of NEW custom components, not metadata updates
* fix(LE-906): address PR3 review feedback
- remove stale placeholder commentary from custom_component/update
- enforce admin-only restriction for truly custom code updates
- keep known-template refresh/update path available for non-admin users
- add endpoint tests for allow/block behavior under admin-only mode
- revert unrelated component_index drift from PR scope
* feat(LE-906): tighten custom component admin gating
* fix(LE-906): tighten custom-component admin gate carveouts
* [autofix.ci] apply automated fixes
* fix(LE-906): address PR3 review comments from Gabriel
- Rename create-side known-template refresh test for accurate intent
- Strengthen superuser coverage with novel-code create case and update case
- Revert unrelated starter project dependency version drift (OpenDsStar)
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* feat(LE-906): MCP lock enforcement + UI/tests
- Add mcp_servers_locked gate to POST /{project_id}/install endpoint
- Extend lock check to PATCH /{project_id} endpoint for auth settings updates
- Non-superuser requests blocked with 403 when flag is enabled
- Add hardened feature-flag checks using getattr(..., False) is True pattern
to prevent MagicMock truthiness in tests
- Update AddMcpServerModal to show locked message when mcp_servers_locked=true
- Fix JSX structure in modal (missing fragment wrapper)
- All MCP modifications now gated when mcp_servers_locked flag enabled
* fix(LE-906): address PR2 review feedback
- add is_mcp_servers_locked helper with MagicMock-safe semantics and rationale
- add regression tests for explicit-true vs MagicMock placeholder behavior
- add missing mcp.modal.lockedTitle/lockedDescription i18n keys across locales
- remove debug leftover test_page.html
- revert unrelated component_index drift from PR scope
* fix(LE-906): enforce MCP lock in v2 servers
* fix(LE-906): address PR2 review comments from Gabriel
- Remove duplicate is_mcp_servers_locked helper from v1/mcp_projects.py;
import from api/v2/mcp instead so unit tests protect production code
- Retarget test imports to langflow.api.v2.mcp.is_mcp_servers_locked
- Add test_v2_mcp_servers_unlocked_allows_non_superuser_add_patch_delete
to cover the flag-off case (non-superuser can CRUD when gate is off)
* fix(LE-906): make MCP lock configurable in PR2
- Declare mcp_servers_locked in Settings so LANGFLOW_MCP_SERVERS_LOCKED is honored
- Add regression test to assert Settings exposes and reads mcp lock env var
* chore(ci): trigger PR2 CI
* feat(LE-906): Config/settings plumbing
- Add 7 new boolean settings: custom_component_admin_only, embedded_mode,
hide_logout_button, hide_new_project_button, hide_new_flow_button,
hide_starter_projects, mcp_servers_locked (all default: false)
- All settings configurable via LANGFLOW_* env vars
- Expose all flags through ConfigResponse schema
- Fix hide_getting_started_progress to read from settings not env
- Add frontend type definitions for all 7 flags
- Initialize utility store with all new flags
- Implement hydration logic for new flags in config hook
* [autofix.ci] apply automated fixes (attempt 2/3)
* fix(LE-906): address PR1 review feedback
- replace internal ICA wording with neutral embedded-mode language
- add settings field for hide_getting_started_progress
- map hide_getting_started_progress from settings in ConfigResponse
- add regression test for embedded_mode hide-flag cascade
- revert unrelated component_index drift from PR scope
* fix(LE-906): address remaining PR1 Gabriel comments
* docs/test(LE-906): clarify embedded_mode scope for QA
- Clarify in settings docs that embedded_mode cascades UI hide flags only
- Keep mcp_servers_locked and custom_component_admin_only as explicit opt-ins
- Add config test assertions that security flags do not auto-cascade
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
The PyPI langflow-nightly is pip-installable again, so the temporary
`if: false` guard added on 2026-05-28 is no longer needed.
#13418 (release-1.10.0) and its forward-port #13419 (main) gave the
extension bundles their own nightly track: langflow-nightly now depends on
lfx-arxiv-nightly / lfx-duckduckgo-nightly / lfx-ibm-nightly, which pin
lfx-nightly==0.5.0.dev* instead of an unsatisfiable stable lfx>=0.5.0,<0.6.0.
Verified against the published dev57 wheels: a dry-run resolve of
langflow-nightly[postgresql] succeeds (549 packages, exit 0), pulling
lfx-nightly==0.5.0.dev57 via the *-nightly bundle variants.
Previously, on first load with no saved language preference, the app
used navigator.language as the fallback, causing users with Portuguese
(pt-PT / pt-BR) or other non-English browser locales to see the UI in
that language automatically. The expected behavior is that English is
always the default; users can change the language explicitly via
settings.
Co-authored-by: Antônio Alexandre Borges Lima <104531655+AntonioABLima@users.noreply.github.com>