windmill-labs/windmill · error

Duplicate group id: '${g.id}'

Error message

Duplicate group id: '${g.id}'

What it means

buildStructureTreeRecurse validates the group list before partitioning nodes, rejecting duplicate group ids because groups are consumed and tracked by id — duplicates would corrupt the consumption bookkeeping and produce ambiguous structure trees.

Source

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

function buildStructureTreeRecurse(
	modules: FlowModule[],
	groups: GraphGroup[]
): { items: FlowStructureNode[]; consumed: Set<string> } {
	if (modules.length === 0) {
		return { items: [], consumed: new Set() }
	}

	const indexMap = new Map<string, number>()
	for (let i = 0; i < modules.length; i++) {
		indexMap.set(modules[i].id, i)
	}

	// Reject duplicate group IDs
	const seenGroupIds = new Set<string>()
	for (const g of groups) {
		if (seenGroupIds.has(g.id)) {
			throw new Error(`Duplicate group id: '${g.id}'`)
		}
		seenGroupIds.add(g.id)
	}

	// 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)) {

View on GitHub (pinned to e474e8803c)

Solutions

  1. Deduplicate groups by id before calling (e.g., new Map(groups.map(g => [g.id, g])).values()).
  2. Replace existing groups with the same id on selection instead of appending.
  3. Ensure group ids are generated from a reliable unique source (crypto.randomUUID or equivalent).

Example fix

// before
const tree = buildStructureTree(modules, [...savedGroups, newGroup])
// after
const merged = new Map([...savedGroups, newGroup].map((g) => [g.id, g]))
const tree = buildStructureTree(modules, [...merged.values()])
Defensive patterns

Strategy: validation

Validate before calling

const uniqueGroups = [...new Map(groups.map((g) => [g.id, g])).values()]
const tree = buildStructureTree(modules, uniqueGroups)

Type guard

function hasUniqueGroupIds(groups: GraphGroup[]): boolean {
  return new Set(groups.map((g) => g.id)).size === groups.length
}

Try / catch

try {
  const tree = buildStructureTree(modules, groups)
} catch (e) {
  if (e.message.includes('Duplicate group id')) {
    return buildStructureTree(modules, dedupeById(groups))
  }
  throw e
}

Prevention

When it happens

Trigger: Passing a GraphGroup array containing two entries with the same id, e.g., from a buggy merge of group sets, a re-selection that appends instead of replaces, or data loaded twice from storage.

Common situations: Group list built by concatenating saved groups with newly created ones without deduplication; id-generation collisions from a faulty uuid source; importing flow state that already contains the group being added.

Related errors


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