Bug Description
Any flow whose data.nodes lack a position field crashes the canvas with a full-page "Sorry, we found an unexpected error!" when opened at /flow/{id}.
The frontend already intends to handle this case — needsLayout() + getLayoutedNodes() exist precisely to lay out position-less flows — but that fallback is broken in two independent ways, so it never recovers.
1. getLayoutedNodes sends id: undefined to ELK. In src/frontend/src/utils/layoutUtils.ts, edges are mapped with id: e.id, and ports are derived with id: e.sourceHandle / id: e.targetHandle. When a flow's edges have none of those fields, ELK rejects the graph:
org.eclipse.elk.graph.json.JsonImportException: Id must be a string or an integer: 'null'.
Both sources must be fixed — repairing either alone still throws. Bisected against a real payload:
baseline (as shipped): THREW -> JsonImportException: Id must be a string or an integer: 'null'.
edge ids fixed only: THREW -> JsonImportException: Id must be a string or an integer: 'null'.
ports filtered only: THREW -> JsonImportException: Id must be a string or an integer: 'null'.
both fixed: OK -> ChatInput(12,12) PromptComponent(416,140) ...
The exception is swallowed by the .catch((e) => console.error(e)) in processFlows, so it only logs.
2. The layout is applied too late — this is the root cause of the crash. processFlows (src/frontend/src/utils/reactflowUtils.ts) iterates with DbData.forEach(async (flow) => { … await processDataFromFlow(…) }). Array.prototype.forEach discards the returned promise, so processFlows returns before the layout resolves. useApplyFlowToCanvas then calls setCurrentFlow() immediately → resetFlow() → position-less nodes reach the flow store → React Flow's adoptUserNodes calls getNodePositionWithOrigin, which dereferences node.position.x with no guard:
TypeError: Cannot read properties of undefined (reading 'x')
at getNodePositionWithOrigin (@xyflow/system)
at adoptUserNodes
A Jest test against the real processFlows confirms the nodes still have position === undefined synchronously after it returns. So the crash happens even if ELK succeeds — the two defects are independent and both need fixing.
A "Maximum update depth exceeded" loop follows from the error boundary re-rendering.
Reproduction
A payload with this shape is served by Langflow's own API, which makes it easy to reproduce end to end:
GET /api/v1/starter-projects/ — every node in the returned data lacks position, and every edge lacks id, sourceHandle, and targetHandle. Verified across all 5 projects (4–9 nodes each, 100% missing).
POST /api/v1/flows/ with one of those data blobs. FlowCreate.data is an unvalidated dict | None, so it is stored verbatim.
- Open the new flow at
/flow/{id} → full-page error boundary, with the three console errors above.
Confirming the diagnosis: PATCHing the flow with synthetic grid positions on each node makes the canvas render normally.
Note this is a different code path from the 27 initial_setup/starter_projects/*.json files that get seeded into the DB and served via /flows/basic_examples/ — those do carry positions, which is why the templates modal works. /api/v1/starter-projects/ builds its graphs programmatically from initial_setup/starter_projects/*.py, and Graph.dump() returns each vertex's full_data / edge's _data verbatim, which for programmatically-built graphs contain only {id, data} and {source, target, data}. Nothing in the UI consumes that endpoint today (the STARTER_PROJECTS constant in controllers/API/helpers/constants.ts is dead), so it is reachable only via the API.
Position-less flows can also arrive from hand-written JSON, older exports, and third-party tooling — the canvas should not hard-crash on data it already claims to handle.
Expected behavior
Opening a flow whose nodes have no position should auto-lay it out and render, not crash the canvas.
Additional context — two adjacent data-quality bugs in the same endpoint
Both are visible in the /api/v1/starter-projects/ response and look unintended:
name is always null. Graph.dump() only sets name when self.flow_name is truthy, and these programmatic graphs never set one, so the key is never populated.
endpoint_name is the literal string "None". graph_dict["endpoint_name"] = str(endpoint_name) in lfx/graph/graph/base.py stringifies None unconditionally.
Operating System
macOS 15 (Darwin 25.5.0)
Langflow Version
Reproduced on release-1.12.0 @ 720cabc; both frontend defects are byte-identical on main.
Python Version
3.12
Bug Description
Any flow whose
data.nodeslack apositionfield crashes the canvas with a full-page "Sorry, we found an unexpected error!" when opened at/flow/{id}.The frontend already intends to handle this case —
needsLayout()+getLayoutedNodes()exist precisely to lay out position-less flows — but that fallback is broken in two independent ways, so it never recovers.1.
getLayoutedNodessendsid: undefinedto ELK. Insrc/frontend/src/utils/layoutUtils.ts, edges are mapped withid: e.id, and ports are derived withid: e.sourceHandle/id: e.targetHandle. When a flow's edges have none of those fields, ELK rejects the graph:Both sources must be fixed — repairing either alone still throws. Bisected against a real payload:
The exception is swallowed by the
.catch((e) => console.error(e))inprocessFlows, so it only logs.2. The layout is applied too late — this is the root cause of the crash.
processFlows(src/frontend/src/utils/reactflowUtils.ts) iterates withDbData.forEach(async (flow) => { … await processDataFromFlow(…) }).Array.prototype.forEachdiscards the returned promise, soprocessFlowsreturns before the layout resolves.useApplyFlowToCanvasthen callssetCurrentFlow()immediately →resetFlow()→ position-less nodes reach the flow store → React Flow'sadoptUserNodescallsgetNodePositionWithOrigin, which dereferencesnode.position.xwith no guard:A Jest test against the real
processFlowsconfirms the nodes still haveposition === undefinedsynchronously after it returns. So the crash happens even if ELK succeeds — the two defects are independent and both need fixing.A "Maximum update depth exceeded" loop follows from the error boundary re-rendering.
Reproduction
A payload with this shape is served by Langflow's own API, which makes it easy to reproduce end to end:
GET /api/v1/starter-projects/— every node in the returneddatalacksposition, and every edge lacksid,sourceHandle, andtargetHandle. Verified across all 5 projects (4–9 nodes each, 100% missing).POST /api/v1/flows/with one of thosedatablobs.FlowCreate.datais an unvalidateddict | None, so it is stored verbatim./flow/{id}→ full-page error boundary, with the three console errors above.Confirming the diagnosis:
PATCHing the flow with synthetic grid positions on each node makes the canvas render normally.Note this is a different code path from the 27
initial_setup/starter_projects/*.jsonfiles that get seeded into the DB and served via/flows/basic_examples/— those do carry positions, which is why the templates modal works./api/v1/starter-projects/builds its graphs programmatically frominitial_setup/starter_projects/*.py, andGraph.dump()returns each vertex'sfull_data/ edge's_dataverbatim, which for programmatically-built graphs contain only{id, data}and{source, target, data}. Nothing in the UI consumes that endpoint today (theSTARTER_PROJECTSconstant incontrollers/API/helpers/constants.tsis dead), so it is reachable only via the API.Position-less flows can also arrive from hand-written JSON, older exports, and third-party tooling — the canvas should not hard-crash on data it already claims to handle.
Expected behavior
Opening a flow whose nodes have no
positionshould auto-lay it out and render, not crash the canvas.Additional context — two adjacent data-quality bugs in the same endpoint
Both are visible in the
/api/v1/starter-projects/response and look unintended:nameis alwaysnull.Graph.dump()only setsnamewhenself.flow_nameis truthy, and these programmatic graphs never set one, so the key is never populated.endpoint_nameis the literal string"None".graph_dict["endpoint_name"] = str(endpoint_name)inlfx/graph/graph/base.pystringifiesNoneunconditionally.Operating System
macOS 15 (Darwin 25.5.0)
Langflow Version
Reproduced on
release-1.12.0@720cabc; both frontend defects are byte-identical onmain.Python Version
3.12