windmill-labs/windmill · error

Group '${g.id}' has inverted range: start_id='${g.start_id}'

Error message

Group '${g.id}' has inverted range: start_id='${g.start_id}' (index ${s}) > end_id='${g.end_id}' (index ${e})

What it means

buildStructureTreeRecurse lays out flow groups as [start_id, end_id] ranges over the ordered node index of the current level. It throws when a group's start node appears after its end node in that ordering, which means the group cannot be rendered as a nested rectangle. This guards the graph layout from inconsistent group metadata.

Source

Thrown at frontend/src/lib/components/graph/flowStructure.ts:105

	// Reject groups referencing virtual nodes
	for (const g of groups) {
		if (VIRTUAL_NODE_IDS.has(g.start_id) || VIRTUAL_NODE_IDS.has(g.end_id)) {
			throw new Error(
				`Group '${g.id}' references virtual node: groups cannot include Input, Result, or Trigger`
			)
		}
	}

	// Partition: groups for this level vs rest
	const levelGroups: GraphGroup[] = []
	const otherGroups: GraphGroup[] = []
	for (const g of groups) {
		if (indexMap.has(g.start_id) && indexMap.has(g.end_id)) {
			const s = indexMap.get(g.start_id)!
			const e = indexMap.get(g.end_id)!
			if (s > e) {
				throw new Error(
					`Group '${g.id}' has inverted range: start_id='${g.start_id}' (index ${s}) > end_id='${g.end_id}' (index ${e})`
				)
			}
			levelGroups.push(g)
		} else {
			otherGroups.push(g)
		}
	}

	// Validate no partial overlaps
	for (let i = 0; i < levelGroups.length; i++) {
		for (let j = i + 1; j < levelGroups.length; j++) {
			const a = levelGroups[i]
			const b = levelGroups[j]
			const aStart = indexMap.get(a.start_id)!
			const aEnd = indexMap.get(a.end_id)!
			const bStart = indexMap.get(b.start_id)!
			const bEnd = indexMap.get(b.end_id)!

View on GitHub (pinned to e474e8803c)

Solutions

  1. Open the flow definition and fix the group so start_id points to the earlier step and end_id to the later one (swap them if inverted).
  2. Re-create the offending group in the flow editor so ids are recomputed from the current step order.
  3. Check for deleted/duplicated steps that shifted indices and update group start_id/end_id accordingly.
  4. If produced by a script/import, regenerate group ids against the current flow version before deploying.

Example fix

// before
{ "id": "g1", "start_id": "step3", "end_id": "step1" }
// after
{ "id": "g1", "start_id": "step1", "end_id": "step3" }
Defensive patterns

Strategy: validation

Validate before calling

const idx = id => indexMap.get(id)
for (const g of groups) {
  const s = idx(g.start_id), e = idx(g.end_id)
  if (s !== undefined && e !== undefined && s > e) console.warn('inverted group', g.id)
}

Type guard

function hasValidRange(g, indexMap) {
  const s = indexMap.get(g.start_id), e = indexMap.get(g.end_id)
  return s !== undefined && e !== undefined && s <= e
}

Try / catch

try { tree = buildStructureTree(items, groups) } catch (e) { if (String(e.message).includes('inverted range')) groups = groups.filter(hasValidRange.bind(null, indexMap)); else throw e }

Prevention

When it happens

Trigger: A GraphGroup whose start_id resolves to a higher index in indexMap than its end_id — e.g. group data produced by the server or a migration lists the group's end step before its start step, or ids were edited so start/end are swapped.

Common situations: Hand-edited or API-patched flow group definitions; a step reordered/deleted in the editor while group ids were stored from an older structure; importing a flow JSON whose group ranges do not match the module order.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/a6ec42105be668c2. Report an issue: GitHub.