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
- 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).
- Re-create the offending group in the flow editor so ids are recomputed from the current step order.
- Check for deleted/duplicated steps that shifted indices and update group start_id/end_id accordingly.
- 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
- Always generate group start_id/end_id from the current step order, never store absolute assumptions
- Validate group ranges after any import or merge of flow JSON
- Re-derive groups when steps are reordered or deleted
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
- Groups '${a.id}' and '${b.id}' overlap without nesting
- Duplicated node detected: ${module.id}
- Cycle detected: adding edge from '${sourceId}' to '${targetI
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/a6ec42105be668c2.
Report an issue: GitHub.