Skip to content

Flows with nodes missing position crash the canvas; the auto-layout fallback is broken two ways #14257

Description

@erichare

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:

  1. 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).
  2. POST /api/v1/flows/ with one of those data blobs. FlowCreate.data is an unvalidated dict | None, so it is stored verbatim.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions