windmill-labs/windmill · error

Invalid group at index ${index}: start_id must be a non-empt

Error message

Invalid group at index ${index}: start_id must be a non-empty string

What it means

Each flow group must specify start_id as a non-empty string naming the module where the group (note box) begins. validateFlowGroups throws at the given index when start_id is missing, not a string, or an empty string.

Source

Thrown at frontend/src/lib/components/copilot/chat/flow/helperUtils.ts:102

export function validateFlowGroups(
	rawGroups: unknown,
	moduleIds?: Set<string>
): FlowGroup[] | null {
	if (rawGroups == null) {
		return null
	}

	if (!Array.isArray(rawGroups)) {
		throw new Error('Flow groups must be an array')
	}

	return rawGroups.map((group, index) => {
		if (!group || typeof group !== 'object' || Array.isArray(group)) {
			throw new Error(`Invalid group at index ${index}: must be an object`)
		}
		const g = group as Record<string, unknown>
		if (typeof g.start_id !== 'string' || !g.start_id) {
			throw new Error(`Invalid group at index ${index}: start_id must be a non-empty string`)
		}
		if (typeof g.end_id !== 'string' || !g.end_id) {
			throw new Error(`Invalid group at index ${index}: end_id must be a non-empty string`)
		}
		if (moduleIds) {
			if (!moduleIds.has(g.start_id)) {
				throw new Error(
					`Invalid group at index ${index}: start_id "${g.start_id}" does not match any flow module`
				)
			}
			if (!moduleIds.has(g.end_id)) {
				throw new Error(
					`Invalid group at index ${index}: end_id "${g.end_id}" does not match any flow module`
				)
			}
		}
		if (g.color !== undefined && g.color !== null) {
			if (typeof g.color !== 'string' || !ALLOWED_NOTE_COLORS.has(g.color)) {

View on GitHub (pinned to e474e8803c)

Solutions

  1. Set start_id to the string id of an existing flow module
  2. Remove the group if it has no meaningful start
  3. Ensure the id matches the module's id exactly (case-sensitive)

Example fix

// before
groups: [{ end_id: 'b' }]
// after
groups: [{ start_id: 'a', end_id: 'b' }]
Defensive patterns

Strategy: validation

Validate before calling

for (const [i, g] of (groups ?? []).entries()) {
  if (typeof g.start_id !== 'string' || !g.start_id)
    throw new Error(`groups[${i}].start_id must be a non-empty string`)
}

Type guard

function hasValidStartId(g) {
  return typeof g?.start_id === 'string' && g.start_id.length > 0
}

Try / catch

try {
  const groups = validateFlowGroups(raw, moduleIds)
} catch (e) {
  if (e.message.includes('start_id must be a non-empty string')) {
    const idx = Number(e.message.match(/index (\d+)/)?.[1])
    raw[idx].start_id = raw[idx].end_id // single-module group fallback
    return validateFlowGroups(raw, moduleIds)
  }
  throw e
}

Prevention

When it happens

Trigger: Calling validateFlowGroups with a group object lacking start_id, having start_id: "", or a non-string value like start_id: 3 or null.

Common situations: LLM emitting only an end_id; empty-string ids from template placeholders that were never substituted; numeric ids from another system.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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