{"record":{"id":"94ae188a21dda4f8","repo":"windmill-labs/windmill","slug":"flow-groups-must-be-an-array","errorCode":null,"errorMessage":"Flow groups must be an array","messagePattern":"Flow groups must be an array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/flow/helperUtils.ts","lineNumber":93,"sourceCode":"\tif (!module || module.value.type !== 'rawscript') {\n\t\treturn undefined\n\t}\n\n\tconst rawScriptModule = module as FlowModule & { value: RawScript }\n\trawScriptModule.value.content = code\n\treturn rawScriptModule\n}\n\nexport function validateFlowGroups(\n\trawGroups: unknown,\n\tmoduleIds?: Set<string>\n): FlowGroup[] | null {\n\tif (rawGroups == null) {\n\t\treturn null\n\t}\n\n\tif (!Array.isArray(rawGroups)) {\n\t\tthrow new Error('Flow groups must be an array')\n\t}\n\n\treturn rawGroups.map((group, index) => {\n\t\tif (!group || typeof group !== 'object' || Array.isArray(group)) {\n\t\t\tthrow new Error(`Invalid group at index ${index}: must be an object`)\n\t\t}\n\t\tconst g = group as Record<string, unknown>\n\t\tif (typeof g.start_id !== 'string' || !g.start_id) {\n\t\t\tthrow new Error(`Invalid group at index ${index}: start_id must be a non-empty string`)\n\t\t}\n\t\tif (typeof g.end_id !== 'string' || !g.end_id) {\n\t\t\tthrow new Error(`Invalid group at index ${index}: end_id must be a non-empty string`)\n\t\t}\n\t\tif (moduleIds) {\n\t\t\tif (!moduleIds.has(g.start_id)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid group at index ${index}: start_id \"${g.start_id}\" does not match any flow module`\n\t\t\t\t)","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/flow/helperUtils.ts#L75-L111","documentation":"validateFlowGroups in helperUtils.ts normalizes the flow's visual 'groups' (editor note boxes) and first checks the raw input shape. Groups are optional; when provided (non-null) they must be a JSON array. Anything else — a string, object, number — is rejected with this error before per-group validation.","triggerScenarios":"Calling validateFlowGroups (via the flowTools 'groups' argument or result validation) with groups set to a non-array non-null value, e.g. groups: {} or groups: \"default\".","commonSituations":"LLM emitting groups as a map keyed by name instead of an array; client sending the whole groups object wrapper rather than its array value; JSON schema mismatch after an API version change.","solutions":["Pass groups as an array: [{ start_id, end_id, ... }]","Omit/null out groups entirely if there are no groups","Unwrap a wrapper object (e.g. { groups: [...] }) to the inner array before calling"],"exampleFix":"// before\nvalidateFlowGroups({ default: { start_id: 'a', end_id: 'b' } })\n// after\nvalidateFlowGroups([{ summary: 'default', start_id: 'a', end_id: 'b' }])","handlingStrategy":"validation","validationCode":"if (groups != null && !Array.isArray(groups)) throw new Error('groups must be an array or null')","typeGuard":"function isFlowGroups(v) {\n  return v == null || (Array.isArray(v))\n}","tryCatchPattern":"try {\n  const groups = validateFlowGroups(raw.groups, moduleIds)\n} catch (e) {\n  if (e.message === 'Flow groups must be an array') {\n    return null // drop groups and continue without visual grouping\n  }\n  throw e\n}","preventionTips":["Always serialize groups as a JSON array of objects","Pass null/undefined when there are no groups instead of {}","Unwrap any { groups: [...] } wrapper before calling the tool"],"tags":["validation","flow-groups","type-error"],"backgroundTag":"schema-validation-failed","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}