langflow-ai/langflow · error · HTTPException
str(exc)
Error message
str(exc)
What it means
HTTP 423 (Locked): the flow row is protected by a domain lock (LockedFlowError from ensure_flow_update_allowed) and the requested update would modify a locked field. _ensure_api_flow_update_allowed translates the domain-layer lock guard into the API's 423 so clients can distinguish 'locked, retry later or unlock' from a plain 400/403.
Source
Thrown at src/backend/base/langflow/api/v1/flows_helpers.py:166
def _apply_update_data(target: Flow, update_data: dict[str, Any]) -> None:
"""Apply *update_data* to the ORM *target*, restricted to the allowlist."""
for key, value in update_data.items():
if key in _UPDATABLE_FLOW_FIELDS:
setattr(target, key, value)
def _endpoint_name_was_explicitly_cleared(flow: FlowCreate | FlowUpdate) -> bool:
"""Return whether the request explicitly asked to clear the endpoint name."""
return "endpoint_name" in flow.model_fields_set and flow.endpoint_name in (None, "")
def _ensure_api_flow_update_allowed(db_flow: Flow, update_data: dict[str, Any]) -> None:
"""Translate the domain lock guard into the API's 423 response."""
try:
ensure_flow_update_allowed(db_flow, update_data)
except LockedFlowError as exc:
raise HTTPException(status_code=423, detail=str(exc)) from exc
async def _verify_fs_path(path: str | None, user_id: UUID, storage_service: StorageService) -> None:
"""Verify and prepare the filesystem path for flow storage."""
if path is not None:
# Empty strings should be rejected (None is allowed, empty string is not)
if path == "":
raise HTTPException(status_code=400, detail="fs_path cannot be empty")
safe_path = _get_safe_flow_path(path, user_id, storage_service)
await safe_path.parent.mkdir(parents=True, exist_ok=True)
if not await safe_path.exists():
await safe_path.touch()
async def _save_flow_to_fs(flow: Flow, user_id: UUID, storage_service: StorageService) -> None:
"""Save flow data to the filesystem at the validated path."""
if not flow.fs_path:
returnView on GitHub (pinned to 976ec789d2)
Solutions
- Acquire/release the flow lock through the flows API (lock endpoint or the documented lock lifecycle) before mutating locked fields.
- If the lock is stale after a crashed job, clear it via the admin flow-lock management endpoint.
- Retry the update after the locking process finishes (423 is transient by design).
- Restrict your PATCH payload to non-locked fields if you do not need to change them.
Example fix
# before
PATCH /api/v1/flows/{id} {"data": {...}} # -> 423
# after
POST /api/v1/flows/{id}/unlock (or wait for lock holder)
PATCH /api/v1/flows/{id} {"data": {...}} Defensive patterns
Strategy: retry
Validate before calling
const lock = await fetch(`/api/v1/flows/${id}`).then(r => r.json());
if (lock.locked) await waitForUnlock(id); Try / catch
for (let i = 0; i < 3; i++) { try { return await patchFlow(id, body); } catch (e) { if (e.status !== 423 || i === 2) throw e; await sleep(backoff(i)); } } Prevention
- Treat 423 as transient; retry with backoff
- Acquire locks through the API before bulk edits
- Clear stale locks after crashed sync jobs
When it happens
Trigger: PATCH/PUT /api/v1/flows/{id} (or a code path calling _ensure_api_flow_update_allowed) where update_data touches fields locked on that flow — e.g. updating flow.data or name while the flow is locked for deployment/sync.
Common situations: Flow is deployed or managed by an external sync (a2a/deployment pipeline) that holds a lock; two writers race — one holds the lock, the other gets 423; automation scripts updating flows that an admin locked via the management UI or API.
Related errors
- Flow not found.
- Name conflict — another role already uses this name
- Role still has active assignments — revoke them before delet
- Flow not found
- You don't have permission to edit this flow.
AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14).
Data as JSON: /api/errors/0abd626504dc0c24.
Report an issue: GitHub.