{"record":{"id":"9a975611b7a04932","repo":"windmill-labs/windmill","slug":"invalid-group-at-index-index-color-must-be-one","errorCode":null,"errorMessage":"Invalid group at index ${index}: color must be one of ${[...ALLOWED_NOTE_COLORS].join(', ')}","messagePattern":"Invalid group at index (.+?): color must be one of (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/flow/helperUtils.ts","lineNumber":128,"sourceCode":"\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)\n\t\t\t}\n\t\t\tif (!moduleIds.has(g.end_id)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid group at index ${index}: end_id \"${g.end_id}\" does not match any flow module`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tif (g.color !== undefined && g.color !== null) {\n\t\t\tif (typeof g.color !== 'string' || !ALLOWED_NOTE_COLORS.has(g.color)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid group at index ${index}: color must be one of ${[...ALLOWED_NOTE_COLORS].join(', ')}`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn g as unknown as FlowGroup\n\t})\n}\n\n/**\n * Validate the optional array of sticky notes the agent attached to the flow.\n * Notes are editor-only annotations and do not affect execution.\n *\n * `free` notes are the supported kind (standalone canvas annotations). The\n * `group` note type is deprecated for creation — the chat prompt steers the\n * agent toward `groups` instead — but it is still ACCEPTED here so flows that\n * already contain group notes round-trip cleanly through `patch_flow_json` /\n * `set_flow_json` rather than being rejected. When `moduleIds` is provided,\n * every `contained_node_ids` entry of a `group` note must reference an existing\n * module.\n *\n * A provided palette `color` is always preserved as-is; the default is only\n * filled in when a note omits `color` entirely (FlowNote.color is required).\n *\n * Free notes are also given a concrete `position` and `size` when missing. A\n * free note without geometry is not draggable/resizable in the editor (you'd","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/flow/helperUtils.ts#L110-L146","documentation":"validateFlowGroups restricts group colors to the NoteColor palette (yellow, blue, green, purple, pink, orange, red, cyan, lime, gray) because the renderer keys its styles by these exact names; arbitrary strings like hex codes would render unstyled or break the color picker. A non-string color, or a string outside the palette, throws this error.","triggerScenarios":"groups: [{ start_id, end_id, color: '#ff0000' }] or color: 'azure' or color: 3 passed to patch_flow_json/set_flow_json.","commonSituations":"An LLM agent outputs hex/CSS color values instead of palette names; a caller passes a numeric enum index instead of the string name; copy-pasted group config from another tool uses different color vocabulary.","solutions":["Use one of the palette names exactly: yellow, blue, green, purple, pink, orange, red, cyan, lime, gray","Omit the color field entirely (undefined/null is accepted) to get the default color","Map any hex/CSS value to the nearest palette name before sending"],"exampleFix":"// before\ngroups: [{ start_id: 'a', end_id: 'b', color: '#3498db' }]\n// after\ngroups: [{ start_id: 'a', end_id: 'b', color: 'blue' }]","handlingStrategy":"validation","validationCode":"const ALLOWED = ['yellow','blue','green','purple','pink','orange','red','cyan','lime','gray'];\nfor (const [i, g] of (groups ?? []).entries()) {\n  if (g.color != null && !ALLOWED.includes(g.color)) throw new Error(`groups[${i}].color \"${g.color}\" not in palette`);\n}","typeGuard":"function isNoteColor(c: unknown): c is NoteColor {\n  return typeof c === 'string' && (Object.values(NoteColor) as string[]).includes(c);\n}","tryCatchPattern":"try {\n  validateFlowGroups(rawGroups, moduleIds);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('color must be one of')) {\n    // retry with color omitted to accept the default\n  } else throw e;\n}","preventionTips":["Only use the ten documented palette names, lowercase","Omit color rather than guessing a hex value","Keep a shared constant list of allowed colors in tool-call-building code"],"tags":["validation","flow-groups","enum-value","color"],"backgroundTag":"invalid-enum-value","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"}