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:
        return

View on GitHub (pinned to 976ec789d2)

Solutions

  1. Acquire/release the flow lock through the flows API (lock endpoint or the documented lock lifecycle) before mutating locked fields.
  2. If the lock is stale after a crashed job, clear it via the admin flow-lock management endpoint.
  3. Retry the update after the locking process finishes (423 is transient by design).
  4. 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

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


AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14). Data as JSON: /api/errors/0abd626504dc0c24. Report an issue: GitHub.