windmill-labs/windmill · error · Error
Invalid group at index
Error message
Invalid group at index ${index}: start_id "${g.start_id}" does not match any flow module What it means
validateFlowGroups validates the `groups` array an AI agent passes to patch_flow_json/set_flow_json. Each group's start_id and end_id must reference an existing flow module; when a moduleIds set is supplied and start_id is not found in it, this error is thrown. It exists to prevent groups that visually dangle on the canvas because they point at modules that don't exist.
Solutions
- Copy module ids verbatim from the flow value the tools returned (each module's `id` field), never invent them
- Re-fetch the current flow JSON to see which module ids actually exist before setting groups
- If the module was intended to be new, add the module itself in the same update and use its generated id
- Drop or fix the offending group entry at the reported index
Example fix
// before
groups: [{ start_id: 'step_1', end_id: 'step_2' }]
// after — use real ids from flow.value.modules
groups: [{ start_id: flow.value.modules[0].id, end_id: flow.value.modules[1].id }] Defensive patterns
Strategy: validation
Validate before calling
const moduleIds = new Set<string>(); forEachFlowModule(flow.value, (m) => { moduleIds.add(m.id); });
for (const [i, g] of (groups ?? []).entries()) {
if (!moduleIds.has(g.start_id)) throw new Error(`groups[${i}].start_id "${g.start_id}" is not a flow module id`);
} Type guard
function isValidGroupId(id: unknown, moduleIds: Set<string>): id is string {
return typeof id === 'string' && moduleIds.has(id);
} Try / catch
try {
const validated = validateFlowGroups(rawGroups, moduleIds);
} catch (e) {
if (e instanceof Error && e.message.includes('does not match any flow module')) {
// surface index + offending id back to the agent so it can re-read the flow
} else throw e;
} Prevention
- Always build groups from the module ids present in the latest flow value you received from the tool
- Copy ids verbatim; never abbreviate or re-generate them
- If you added modules in the same update, use the exact ids you assigned them
When it happens
Trigger: Calling updateFlowJson/patch_flow_json with groups: [{start_id: 'mod_x', end_id: 'mod_y'}] where 'mod_x' is not a module id in the current flow (typo, hallucinated id by the LLM, or a module deleted earlier in the same edit).
Common situations: An LLM agent invents plausible-looking module ids instead of copying them from the flow it was given; the agent references a module from a previous flow revision that was renamed or removed; the caller forgets to include newly-added module ids in the moduleIds set.
Related errors
- Invalid group at index
- Flow groups must be an array
- Invalid group at index
- Invalid group at index
- Invalid group at index
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/d38f6dd33f2e3599.
Report an issue: GitHub.
Appendix: source
Thrown at frontend/src/lib/components/copilot/chat/flow/helperUtils.ts:121
}
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)) {
throw new Error(
`Invalid group at index ${index}: color must be one of ${[...ALLOWED_NOTE_COLORS].join(', ')}`
)
}
}
return g as unknown as FlowGroup
})
}
/**
* Validate the optional array of sticky notes the agent attached to the flow.
* Notes are editor-only annotations and do not affect execution.
*
* `free` notes are the supported kind (standalone canvas annotations). The
* `group` note type is deprecated for creation — the chat prompt steers the
* agent toward `groups` instead — but it is still ACCEPTED here so flows that
* already contain group notes round-trip cleanly through `patch_flow_json` /
* `set_flow_json` rather than being rejected. When `moduleIds` is provided,
* every `contained_node_ids` entry of a `group` note must reference an existingView on GitHub (pinned to e474e8803c)