{"record":{"id":"0abd626504dc0c24","repo":"langflow-ai/langflow","slug":"str-exc-0abd62","errorCode":null,"errorMessage":"str(exc)","messagePattern":"str\\(exc\\)","errorType":"http","errorClass":"HTTPException","httpStatus":423,"severity":"error","filePath":"src/backend/base/langflow/api/v1/flows_helpers.py","lineNumber":166,"sourceCode":"\ndef _apply_update_data(target: Flow, update_data: dict[str, Any]) -> None:\n    \"\"\"Apply *update_data* to the ORM *target*, restricted to the allowlist.\"\"\"\n    for key, value in update_data.items():\n        if key in _UPDATABLE_FLOW_FIELDS:\n            setattr(target, key, value)\n\n\ndef _endpoint_name_was_explicitly_cleared(flow: FlowCreate | FlowUpdate) -> bool:\n    \"\"\"Return whether the request explicitly asked to clear the endpoint name.\"\"\"\n    return \"endpoint_name\" in flow.model_fields_set and flow.endpoint_name in (None, \"\")\n\n\ndef _ensure_api_flow_update_allowed(db_flow: Flow, update_data: dict[str, Any]) -> None:\n    \"\"\"Translate the domain lock guard into the API's 423 response.\"\"\"\n    try:\n        ensure_flow_update_allowed(db_flow, update_data)\n    except LockedFlowError as exc:\n        raise HTTPException(status_code=423, detail=str(exc)) from exc\n\n\nasync def _verify_fs_path(path: str | None, user_id: UUID, storage_service: StorageService) -> None:\n    \"\"\"Verify and prepare the filesystem path for flow storage.\"\"\"\n    if path is not None:\n        # Empty strings should be rejected (None is allowed, empty string is not)\n        if path == \"\":\n            raise HTTPException(status_code=400, detail=\"fs_path cannot be empty\")\n        safe_path = _get_safe_flow_path(path, user_id, storage_service)\n        await safe_path.parent.mkdir(parents=True, exist_ok=True)\n        if not await safe_path.exists():\n            await safe_path.touch()\n\n\nasync def _save_flow_to_fs(flow: Flow, user_id: UUID, storage_service: StorageService) -> None:\n    \"\"\"Save flow data to the filesystem at the validated path.\"\"\"\n    if not flow.fs_path:\n        return","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/v1/flows_helpers.py#L148-L184","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nPATCH /api/v1/flows/{id}  {\"data\": {...}}   # -> 423\n# after\nPOST /api/v1/flows/{id}/unlock   (or wait for lock holder)\nPATCH /api/v1/flows/{id}  {\"data\": {...}}","handlingStrategy":"retry","validationCode":"const lock = await fetch(`/api/v1/flows/${id}`).then(r => r.json());\nif (lock.locked) await waitForUnlock(id);","typeGuard":null,"tryCatchPattern":"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)); } }","preventionTips":["Treat 423 as transient; retry with backoff","Acquire locks through the API before bulk edits","Clear stale locks after crashed sync jobs"],"tags":["concurrency","locking","http-423","flows","conflict"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}