windmill-labs/windmill · error · Error
Invalid note at index ${index}: type must be "free" or "grou
Error message
Invalid note at index ${index}: type must be "free" or "group" What it means
The only accepted note types are 'free' (standalone canvas annotation, the default) and 'group' (deprecated for creation but still accepted for round-tripping existing flows). validateFlowNotes throws this error when note.type is any other value.
Source
Thrown at frontend/src/lib/components/copilot/chat/flow/helperUtils.ts:184
let autoStackY = 0
return rawNotes.map((note, index) => {
if (!note || typeof note !== 'object' || Array.isArray(note)) {
throw new Error(`Invalid note at index ${index}: must be an object`)
}
const n = note as Record<string, unknown>
if (typeof n.id !== 'string' || !n.id) {
throw new Error(`Invalid note at index ${index}: id must be a non-empty string`)
}
if (seenIds.has(n.id)) {
throw new Error(`Invalid note at index ${index}: duplicate note id "${n.id}"`)
}
seenIds.add(n.id)
if (typeof n.text !== 'string') {
throw new Error(`Invalid note at index ${index}: text must be a string`)
}
const type = n.type ?? 'free'
if (type !== 'free' && type !== 'group') {
throw new Error(`Invalid note at index ${index}: type must be "free" or "group"`)
}
if (n.color !== undefined && n.color !== null) {
if (typeof n.color !== 'string' || !ALLOWED_NOTE_COLORS.has(n.color)) {
throw new Error(
`Invalid note at index ${index}: color must be one of ${[...ALLOWED_NOTE_COLORS].join(', ')}`
)
}
}
if (n.position !== undefined && n.position !== null) {
const p = n.position as Record<string, unknown>
if (
typeof p !== 'object' ||
Array.isArray(n.position) ||
typeof p.x !== 'number' ||
typeof p.y !== 'number'
) {
throw new Error(
`Invalid note at index ${index}: position must be an object with numeric x and y`View on GitHub (pinned to e474e8803c)
Solutions
- Use type: 'free' (or omit type, which defaults to 'free') for standalone notes
- Use type: 'group' only for pre-existing group notes; prefer the separate groups array for new groupings
- Fix casing — the comparison is exact lowercase
Example fix
// before
notes: [{ id: 'n1', text: 'x', type: 'sticky' }]
// after
notes: [{ id: 'n1', text: 'x', type: 'free' }] // or omit type Defensive patterns
Strategy: validation
Validate before calling
const TYPES = ['free', 'group'];
notes.forEach((n, i) => {
if (n.type !== undefined && !TYPES.includes(n.type)) throw new Error(`notes[${i}].type "${n.type}" invalid`);
}); Type guard
function isValidNoteType(t: unknown): t is 'free' | 'group' {
return t === 'free' || t === 'group';
} Try / catch
try {
validateFlowNotes(rawNotes, moduleIds);
} catch (e) {
if (e instanceof Error && e.message.includes('type must be')) {
// drop the type field (defaults to 'free') and retry
} else throw e;
} Prevention
- Omit type for new notes — 'free' is the default and the recommended kind
- Never invent type names; only 'free' and the deprecated 'group' are accepted
- Use the groups array for grouping logic instead of group-typed notes
When it happens
Trigger: notes: [{ id: 'n1', text: 'x', type: 'sticky' }] or type: 'Free' (wrong case) or type: 'annotation' passed to patch_flow_json/set_flow_json.
Common situations: An LLM agent invents a type name; capitalization mismatch; an agent tries to create the deprecated 'group' notes under a new name instead of using the groups array.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Invalid note at index ${index}: color must be one of ${[...A
- Invalid group at index ${index}: color must be one of ${[...
- Flow notes must be an array
- Invalid note at index ${index}: must be an object
- Invalid note at index ${index}: id must be a non-empty strin
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/87ead04620936dee.
Report an issue: GitHub.